6.二分+贪心

6.二分+贪心

  • 二分查找
  • 贪心算法
  • 练习
    • 进击的奶牛 (二分)洛谷P1824
    • A - Trailing Zeroes (III)(判断n!末尾0的个数⭐⭐)(二分)
    • B - Strange fuction(二分)
    • C - Pie(二分)⭐⭐
    • D - Best Cow Line(贪心)
    • E - The Frog's Games(二分)
    • F - 湫湫系列故事——消灭兔子(贪心+运算符重载)⭐⭐
    • G - pairs

二分查找

二分查找的基本思想:
将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;如果xa[n/2],则只要在数组a的右半部搜索x。
时间复杂度:O(log2(n))

贪心算法

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解 。

例如,平时购物找零钱时,为使找回的零钱的硬币数最少,不要求找零钱的所有方案,而是从最大面值的币种开始,按递减的顺序考虑各面额,先尽量用大面值的面额,当不足大面值时才去考虑下一个较小面值,这就是贪心算法 。

练习

进击的奶牛 (二分)洛谷P1824

题目描述
Farmer John建造了一个有N(2<=N<=100,000)个隔间的牛棚,这些隔间分布在一条直线上,坐标是x1,…,xN (0<=xi<=1,000,000,000)。

他的C(2<=C<=N)头牛不满于隔间的位置分布,它们为牛棚里其他的牛的存在而愤怒。为了防止牛之间的互相打斗,Farmer John想把这些牛安置在指定的隔间,所有牛中相邻两头的最近距离越大越好。那么,这个最大的最近距离是多少呢?

输入格式
第1行:两个用空格隔开的数字N和C。

第2~N+1行:每行一个整数,表示每个隔间的坐标。

输出格式
输出只有一行,即相邻两头牛最大的最近距离。

AC代码:

#include 
#include 
using namespace std;
const int N=1e5;
int n,c;
int a[N];

bool check(int x) 	//判断x是否可行 
{
	int cnt=1;	//记录可以安置牛的隔间数,a[0]一定可以放,所以cnt初始化为1 
	int l=0;	//上一个符合条件的隔间的下标 
	for(int i=1;i<n;i++)
	//因为已经排过序,所以我们 直接一个一个向后遍历得到的cnt一定是最大的 
	{
		if(a[i]-a[l]>=x)	//两个隔间之间距离大于x 
		{
			cnt++;
			l=i;
		}
	}
	if(cnt>=c)	return 1;	//如果可以安置牛的隔间数大于牛的个数 
	else	return 0;
}

int main()
{
	cin>>n>>c;
	for(int i=0;i<n;i++)	cin>>a[i];
	
	sort(a,a+n);	//从小到大排序
	 
	int l=1,r=a[n-1]-a[0],mid,ans=-1;
	//l为左界,即距离最小值,r为右界,即距离最大值。 
	while(l<=r)	
	//l=r时也需要判断,因为我们并不清楚该值是否为我们需要查找的答 
	{
		mid=(l+r)/2;	//二分查找 
		if(check(mid))		//如果mid符合条件 
		{
			if(mid>ans) ans=mid;	//我们要的是最大值 
			l=mid+1;	//向右查找有没有更大的 
		}
		else	r=mid-1;	//mid不符合条件,说明mid大了,向左查找 
	}
	
	cout<<ans<<endl;
	return 0;
}

A - Trailing Zeroes (III)(判断n!末尾0的个数⭐⭐)(二分)

给定一个正整数m,你需要找到一个最小的正整数n,满足n!的末尾恰好有m个零。
Input
第一行为一个整数T代表数据组数。(T<=1e4)
接下来T行,每组样例一行。
每组样例输入一个整数m。(1<=m<=1e8)
Output
对于每组样例,按照"Case i: x"或者"Case i: impossible"的格式输出。
(如果不存在任何一个数的阶乘末尾有m个零,输出impossible)
Sample Input
3
1
3
5
Sample Output
Case 1: 5
Case 2: 15
Case 3: impossible
思路:

因为m,t的数据很大,暴力求解一定会TLE,所以我们二分查找

n!的末尾恰好有m个零
==>n!恰好可以整除m次10
因为10=2*5,n!的因子中2的个数一定大于5的个数,
所以因子中有多少个5就能凑够多少个10
==>n!因子中有m个5

n/5  =  n!中有多少个5的倍数
 +
n/25  =  (n/5)/5  =  n!中有多少个25的倍数
 +
.....
 ||
n!的因子中有多少个5
如 n=33
n/5=6		5 10 15 20 25 30
n/25=6/5=1		25
3 10 15 20 25(2) 30
cnt=6+1=7

AC代码:

#include 
using namespace std;
const int N=1e9;
//x的阶乘末尾0的个数,也就是求x!可以整除多少个10
int cnt(int x)	//10的因子为2和5,2的个数一定比5多
{				//所以我们只需找x!因子5的个数 
	int ans=0;
	while(x)		//比如x=44,则ans=44/5+44/25(44中为5的倍数的个数+25的倍数的个数) 
	{				//				 =44/5+(44/5)/5
		ans+=x/5;	//				 = 8+1=9
		x/=5;		//		5 10 15 20 25(2个5) 30 35 40 
	}
	return ans;
}
int main()
{
	int t;
	cin>>t;
	for(int i=1;i<=t;i++)
	{
		int m;
		cin>>m;
		int l=1,r=N,mid;
		while(l<=r)
		{
			mid=(l+r)/2;
			if(check(mid)<m)	l=mid+1;
			else
			{
				if(check(mid)==m)
					ans=mid;
				r=mid-1;
			}
			
		}
		cout<<"Case "<<++i<<": ";
		if(ans==-1)	cout<<"impossible"<<endl;
		else	cout<<ans<<endl;
	}
	return 0;
}

B - Strange fuction(二分)

Now, here is a fuction:

  F(x) = 6 * x^7+8*x^6+7*x^3+5*x^2-y*x (0 <= x <=100)

Can you find the minimum value when x is between 0 and 100.
Input
The first line of the input contains an integer T(1<=T<=100) which means the number of test cases. Then T lines follow, each line has only one real numbers Y.(0 < Y <1e10)
Output
Just the minimum value (accurate up to 4 decimal places),when x is between 0 and 100.
Sample Input
2
100
200
Sample Output
-74.4291
-178.8534
思路:
对F(x)求导,导函数单调递增,导函数为0的点F(x)最小。
AC代码:

#include 
#include 
#include 
using namespace std;
double fun_1(double x,double y)
{
	return 42*pow(x,6)+48*pow(x,5)+21*pow(x,2)+10*x-y;
}
double fun_2(double x,double y)
{
	return 6*pow(x,7)+8*pow(x,6)+7*pow(x,3)+5*pow(x,2)-y*x;
}
int main()
{
	int t;
	scanf("%d",&t);
	double x,y,eps=1e-10;
	while(t--)
	{
		scanf("%lf",&y);
		double l=0,r=100,mid;
		while(r-l>eps)
		{
			mid=(l+r)/2;
			if(fun_1(mid,y)<=0)
				l=mid;	
			else if(fun_1(mid,y)>0)		r=mid;
		}
		printf("%.4lf\n",fun_2(r,y));
	}
	return 0;
}

C - Pie(二分)⭐⭐

雄雄学长的生日到了!根据习俗,他需要将一些小蛋糕分给大家。已知他有 N 个不同口味、不同大小的蛋糕。有 F 个朋友会来参加雄雄学长的派对,每个人会拿到一块蛋糕 (必须是一个蛋糕的一块,不能由几个蛋糕的小块拼成;可以是一整个蛋糕)。

学长的朋友们都特别小气,如果有人拿到更大的一块,其他人就会开始抱怨。因此所有人拿到的蛋糕是同样大小的 (但不必是同样形状的),虽然这样有些蛋糕会被浪费,但总比搞砸整个派对好。当然,雄雄学长自己也要留一块,而这一块也要和其他人的同样大小。

请问每个人拿到的蛋糕最大是多少?( 每个蛋糕都是一个高为 1,半径不等的圆柱体。)

输入
首先输入一个整数 T,表示测试数据的组数。
对于每组测试数据:
第 1 行包含两个正整数 N, F,分别表示派的数量和朋友的数量,满足 1 <= N, F <= 10000。
第 2 行包含 N 个 1 到 10000 之间的整数,表示每个蛋糕的半径。

输出
每组测试数据对应一行,输出每个人能得到的最大的蛋糕的体积,误差不超过 10^(-3)。

AC代码:

//在wa了无数次后把pi从3.1415926改为acos(-1)居然过了....淦
#include 
#include 
#include 
#include 
#include 
using namespace std;
const double pi=acos(-1.0);
double a[10010];
int n,f,r;
bool check(double x)
{
	int cnt=0;
	for(int i=1;i<=n;i++)
		cnt+=(int)(a[i]/x);
	if(cnt>=f+1)	return 1;
	else	return 0;	
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		memset(a,0,sizeof(a));
		scanf("%d%d",&n,&f);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&r);
			a[i]=pi*r*r;
		}
		sort(a+1,a+n+1);
		double l=0,r=a[n],mid;
		while(r-l>1e-6)
		{
			mid=(l+r)/2;
			if(check(mid))
				l=mid;
			else	r=mid;
		}
		printf("%.4lf\n",l);	
	}
	return 0;
}

D - Best Cow Line(贪心)

你暨被一个故意在日志中留名为雪糕怪物的黑客给黑了!进出校门光刷卡不行了,而被锁上了一个密码锁。聪明的学生温大卫与小旋破解了部分的密码,得到了一个长度为N(1≤N≤2000)的字符串S,然而最后的密码是由S中所有字母构成字典序最小的字符串T(起初T是一个空串)。但是要想知道最后的密码只能以下两种操作:
·从S的头部删除一个字符,加到T的尾部
·从S的尾部删除一个字符,加到T的尾部
目标是要构造字典序尽可能小的字符串。JNUACM的未来啊,你能不能帮帮温大卫和小旋呢?
Input
第一行一个整数N(代表字符串S的长度) 接下来的2~N+1行是字符串S中的字母(只包含大写字母)
Output
输出时每行最多80个字符
Sample Input
6
A
C
D
B
C
B
Sample Output
ABCBCD
AC代码:

#include 
#include 
#include 
#include 
using namespace std;
char str[2100];
int n;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)	cin>>str[i];
	int l=1,r=n;
	int cnt=0;
	while(n--)
	{
		if(str[l]<str[r])	cout<<str[l++];
		else if(str[l]>str[r])	cout<<str[r--];
		else
		{
			int i=l,j=r;
			while(str[i]==str[j])
			{
				i++;j--;
			}
			if(str[i]<str[j])	cout<<str[l++];
			else	cout<<str[r--];
		}
		cnt++;
		if(cnt%80==0)	cout<<endl;
	}
	cout<<endl;
	return 0;
}

E - The Frog’s Games(二分)

一只青蛙想过河,但奈何体力不足,对着河犯难,所以请你帮它算一算。 这条河宽度是L,可以理解成一条长度为L(1<=l<=1e9)的直线,起点为0,终点为L。水流很急,一旦落水就会被冲走,不过好在河上有一些石头。石头有n(n <= 500000)个,呈一条直线排列。但由于体力不支,青蛙最多可以跳m次(1<= m <= n+1),也即最多选取n个石头中的m个作为自己的落脚点。 在选取完石头后,青蛙会对这个选取方案进行评估其跳跃难度,难度取决于相邻距离最远的两个石头,现在问所有跳跃方案中,难度最小的跳跃方案的难度是多少?(青蛙最长的跳跃距离)
Input
输入有多组样例,每组样例的第一行为L,n,m,下面n行描述n个石头的位置,位置用一个整数表示其距河岸起点的距离。不会出现两个石头的位置重合。
Output
每组样例请输出最小难度
AC代码:

#include 
#include 
#include 
#include 
using namespace std;
const int N=5e5+10;
int s,n,m,a[N];
bool check(int x)
{
	int last=0,cnt=0,i;
	for(i=1;i<n;i++)
	{
		if(a[i]-a[i-1]>x)	return 0;
		if(a[i]-a[last]<=x&&a[i+1]-a[last]>x)
		{
			cnt++;
			last=i;
		}
	}
	if(a[i]-a[i-1]>x)	return 0;
	cnt++;
	if(cnt<=m)	return 1;
	else	return 0;
}
int main()
{
	while(scanf("%d%d%d",&s,&n,&m)!=EOF)
	{
		memset(a,0,sizeof(a));
		for(int i=1;i<=n;i++)	scanf("%d",&a[i]);
		a[++n]=s;
		sort(a,a+n+1);
		int l=1,r=s,mid;
		while(l<=r)
		{
			mid=(l+r)/2;
//			cout<
			if(check(mid))	r=mid-1;
			else	l=mid+1;
		}
		printf("%d\n",r+1);
//		cout<
	}
	return 0;
}
//0 2 6
//0 2 11 18 25

F - 湫湫系列故事——消灭兔子(贪心+运算符重载)⭐⭐

湫湫减肥
  越减越肥!
  
  最近,减肥失败的湫湫为发泄心中郁闷,在玩一个消灭免子的游戏。
  游戏规则很简单,用箭杀死免子即可。
  箭是一种消耗品,已知有M种不同类型的箭可以选择,并且每种箭都会对兔子造成伤害,对应的伤害值分别为Di(1 <= i <= M),每种箭需要一定的QQ币购买。
  假设每种箭只能使用一次,每只免子也只能被射一次,请计算要消灭地图上的所有兔子最少需要的QQ币。
Input
输入数据有多组,每组数据有四行;
第一行有两个整数N,M(1 <= N, M <= 100000),分别表示兔子的个数和箭的种类;
第二行有N个正整数,分别表示兔子的血量Bi(1 <= i <= N);
第三行有M个正整数,表示每把箭所能造成的伤害值Di(1 <= i <= M);
第四行有M个正整数,表示每把箭需要花费的QQ币Pi(1 <= i <= M)。
特别说明:
1、当箭的伤害值大于等于兔子的血量时,就能将兔子杀死;
2、血量Bi,箭的伤害值Di,箭的价格Pi,均小于等于100000。
Output
如果不能杀死所有兔子,请输出”No”,否则,请输出最少的QQ币数,每组输出一行。
AC代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int N=1e5+10;
int b[N];

struct node
{
	int d,p;
	//运算符重载小于号,优先队列本来默认大先出
	//重载后价格小的在前 
	bool operator<(const node & o)const
	{
		return p>o.p;
	}
}jian[N];

bool cmp_1(int x,int y)		 
{
	return x>y;
}

bool cmp_2(node x,node y)
{
	return x.d>y.d;
}

int main()
{
	int n,m;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		for(int i=1;i<=n;i++)	scanf("%d",&b[i]);
		sort(b+1,b+n+1,cmp_1);
//		兔子血量从大到小排列 
		
		for(int i=1;i<=m;i++)	scanf("%d",&jian[i].d);
		for(int i=1;i<=m;i++)	scanf("%d",&jian[i].p);
		sort(jian+1,jian+m+1,cmp_2);
//		伤害值从大到小排列 
		
		int ind=1;
//		箭的下标 
		priority_queue<node> q;
//		优先队列q用于存放可以杀死兔子的箭,且内部按价格从小到大排序 
		bool vis=0;
//		标记能否杀死所有兔子 
		long long ans=0; 
		for(int i=1;i<=n;i++)
		{
			while(jian[ind].d>=b[i]&&ind<=m)
//			只要箭的伤害值大于兔子的血量 
			{
				q.push(jian[ind]);
				ind++;
			}
			
			if(q.empty())
//			如果q是空的,说明剩余伤害值最大的箭都杀不死当前兔子 
			{
				vis=1;
				break;
			}
			else
			{
				ans+=q.top().p;
//				此时的top是所有可以杀死兔子的箭中价格最便宜的 
				q.pop();
//				就算有剩余,剩余的箭也一定能杀死后面的兔子 
			}
		}
		if(vis)	printf("No\n");
		else	printf("%lld\n",ans);
	}
	return 0;
}

G - pairs

John has n points on the X axis, and their coordinates are (x[i],0),(i=0,1,2,…,n−1). He wants to know how many pairs that |x[b]−x[a]|≤k.(a Input
The first line contains a single integer T (about 5), indicating the number of cases.
Each test case begins with two integers n,k(1≤n≤100000,1≤k≤109).
Next n lines contain an integer x[i (−109≤x[i]≤109), means the X coordinates.
Output
For each case, output an integer means how many pairs that |x[b]−x[a]|≤k.
Sample Input
2
5 5
-100
0
100
101
102
5 300
-100
0
100
101
102
Sample Output
3
10

#include 
#include 
using namespace std;
typedef long long ll;
int a[100010];
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		int n,k;
		cin>>n>>k;
		for(int i=0;i<n;i++)	cin>>a[i];
		sort(a,a+n);
		ll ans=0;
//		-100 0 100 101 102
//		i=2 l=3 r=4 m=3 l=4 m=4 l=5 
		for(int i=0;i<n;i++)	//枚举x[a] 
		{
			int l=i+1,r=n-1,mid,t=0;
			while(l<=r)		//二分查找x[b]使x[b]-x[a]=k 
			{
				mid=(l+r)/2;
				if((ll)a[mid]-a[i]<=k)
					l=mid+1;
				else	r=mid-1;
			}
			ans=ans+l-i-1;
//			cout<
		}
		cout<<ans<<endl;
	}
	return 0;
}

你可能感兴趣的:(暑期训练,贪心算法,二分查找)