二分刷题~

 二分本质上是借助你要二分的某X具有的与其单调的某Y性质,来找到是否满足Y的边界。若要暴力,其实也能找到,二分不过是降低了时间复杂度而已。所以做题时,得先会暴力怎么写,过不了再继续二分优化。

//************************************二分答案*****************************************************//

1、洛谷P1873 [COCI 2011/2012 #5] EKO / 砍树

锯片高度与木材长度满足单调递减(单调不增)关系,锯片越高,得到的木材越短,又要找锯片最高高度,所以可以二分答案即锯片高度,找出满足木材长度与不满足之间的分界线。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=1000010;
int n,m,q[N];

bool check(int x)
{
	int sum=0;
	for(int i=0;i<n;i++){
		sum+=max(0,q[i]-x);    //舍去负数结果
		if(sum>=m) return true;    //一旦满足就返回结果,提升速度
	}
	return false;
}
 
int main()
{
	int hest=0;
	cin>>n>>m;
	for(int i=0;i<n;i++){
		cin>>q[i];
		hest=max(hest,q[i]);
	}
	
	int l=0,r=hest+1;
	while(l+1<r){
		int mid=(l+r)/2;
		if(check(mid))
			l=mid;
		else
			r=mid;
	}
	printf("%d",l);
	
	return 0;
} 

2、洛谷P2440 木材加工

 和上一题几乎一模一样。。。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=100010;
int n,k,q[N];

bool check(int x)
{
	int sum=0;
	for(int i=0;i<n;i++){
		sum+=q[i]/x;
		if(sum>=k) return true;
	}
	return false;
}
 
int main()
{
	int lest=0;
	cin>>n>>k;
	for(int i=0;i<n;i++){
		cin>>q[i];
		lest=max(lest,q[i]);
	}
	
	int l=0,r=lest+1;
	while(l+1<r){
		int mid=(l+r)/2;
		if(check(mid))
			l=mid;
		else
			r=mid;
	}
	printf("%d",l);
	
	return 0;
} 

//************************************更复杂的模拟**************************************************//

只要你会暴力(模拟出过程)和二分模板,就能做出来!

3、P2678 [NOIP2015 提高组] 跳石头

最短跳跃距离与移动次数单调不减。

这个check怎么实现呢?check函数每个题有每个题的写法,但大体上的思想应该都是一样的——想办法检测这个解是不是合法。拿这个题来说,我们去判断如果以这个距离为最短跳跃距离需要移走多少块石头,先不必考虑限制移走多少块,等全部拿完再把拿走的数量和限制进行比对,如果超出限制,那么这就是一个非法解,反之就是一个合法解。

去模拟这个跳石头的过程。开始你在i(i=0)位置,我在跳下一步的时候去判断我这个当前跳跃的距离,如果这个跳跃距离比二分出来的mid小,**那这就是一个不合法的石头,应该移走。**为什么?我们二分的是最短跳跃距离,已经是最短了,如果跳跃距离比最短更短岂不是显然不合法,是这样的吧。移走之后要怎么做?先把计数器加上1,再考虑向前跳啊。去看移走之后的下一块石头,再次判断跳过去的距离,如果这次的跳跃距离比最短的长,那么这样跳是完全可以的,我们就跳过去,继续判断,如果跳过去的距离不合法就再拿走,这样不断进行这个操作,直到第n+1次,为啥是n+1?河中间有n块石头,显然终点在n+1处。

模拟完这个过程,我们查看计数器的值,这个值代表的含义是我们以mid作为答案需要移走的石头数量,然后判断这个数量 是不是超了就行。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=50010;
int L,n,m,q[N];

bool check(int x)
{
	int i=0,now=0;
	int cnt=0;
	
	while(i<n+1){
		i++;
		if(q[i]-q[now]<x)
			cnt++;
		else
			now=i;
	}
	if(cnt<=m) 
		return true;
	else 
		return false;
}

int main()
{
	cin>>L>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>q[i];
	q[n+1]=L;
	
	int l=0,r=L+1;
	while(l+1<r){
		int mid=(l+r)/2;
		if(check(mid))
			l=mid;
		else
			r=mid;
	}
	cout<<l;
	
	return 0;
}

4、P3853 [TJOI2007]路标设置

最大距离与路标数单调不增。

思路和上一题相似,注意输入的q[N]是与起点的距离不是路标间距;check函数改一改:比最大距离大就插路标。。。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=100010;
int L,n,k,q[N];

bool check(int x)
{
	int dx[N];
	int cnt=0;
	
	for(int i=1;i<n;i++){
		dx[i]=q[i]-q[i-1];
		
		while(dx[i]>x){    //在你放一个路标后其间距仍可能大于x,故需循环处理
			cnt++;
			dx[i]-=x;
		}
	}
	if(cnt<=k)
		return true;
	else
		return false;
}

int main()
{
	cin>>L>>n>>k;
	for(int i=0;i<n;i++)
		cin>>q[i];
	
	int l=0,r=L+1;
	while(l+1<r){
		int mid=(l+r)/2;
		if(check(mid))
			r=mid;
		else
			l=mid;
	}
	cout<<r;
	
	return 0;
}

5、P1182 数列分段 Section II

和4题思路相同,有些细节注意下。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=100010;
int n,m,q[N];
int l,r;

bool check(int x)
{
	int now=0;
	int cnt=1;		//cnt-->段数,最初就是一段 
	for(int i=0;i<n;i++){
	    
		if(now+q[i]>x){     //当目前的和大于x
			cnt++;         //插线
			now=0;        //重“新”开始
		}
		now+=q[i];      //新-->q[i]
	}
	if(cnt<=m)
		return true;
	else
		return false;
}

int main()
{
	cin>>n>>m;
	for(int i=0;i<n;i++){
		cin>>q[i];
		l=max(l,q[i]);
		r+=q[i];
	}

	l--;r++;    //若l不减1,则第一个答案永远都不在满足check函数的那一边,哪怕它是满足的;r++同理
	while(l+1<r){    
		int mid=(l+r)/2;
		if(check(mid))
			r=mid;
		else
			l=mid;
	}
	cout<<r;
	
	return 0;
}

6、P3743 kotori的设备

由于充电的过程是连续的,并且切换设备的时间忽略不计,所以只要满足我们要充的电的总量比能充的电的总量小,就一定有一种充电方式,不需要担心中途没电的问题:我先给最快没电的那个设备充电,充到它和当前最快没电的设备没电的时间相同时停止充电,然后再给新的最快没电的设备充电。

充电时间和可充总能量单调递增。 

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=100010;
int n;
double p,a[N],b[N],sum;

bool check(double x)
{
	double q=p*x;	//可充的总能量 
	double all=0;	//需消耗的总能量 
	
	for(int i=0;i<n;i++){
		if(a[i]*x<=b[i])	//设备原有能量 能供给消耗 
			continue;
		all+=(a[i]*x-b[i]);	//不能 供给则要耗能 
	}
	if(all<=q)
		return true;
	else
		return false;
}

int main()
{
	cin>>n>>p;
	for(int i=0;i<n;i++){
		cin>>a[i]>>b[i];
		sum+=a[i];
	}
	if(sum<=p){	//需消耗总和小于充电速度 
		cout<<-1;
		return 0;
	}
	
	double l=0,r=1e10;	//过不了 r开大一点 
	while(r-l>1e-6){
		double mid=(l+r)/2;
		if(check(mid))
			l=mid;
		else
			r=mid;
	}
	cout<<l;
	
	return 0;
}

/*
我们的充电并不是一次性充了 a[i]*x-b[i],而是充了多次,他的充电时间和为这个值!为什么可以这样做呢?

首先换手机充电的操作是瞬间完成,其次电量变化是连续的。这也就给了我们充一会一把手机,让它能用到我把其他手机充电到能用到相同时刻的电量的可能。这样我们就可以一直按这样做下去,使其使用时间尽量长。因为使用时间可以二分找到,那么我们对于每把手机的充电量也就可以全部一次性算出来,而不用一份一份算啦。

所以我们在 check 函数里判断时间可不可行虽然是整段算的,但实际上它被分为很多细小的部分分别进行充电。
*/

//***********************************************浮点数二分**********************************************//

7、P1163 银行贷款 

 利率越大,需还款数越大,其单调递增。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

int a,b,c;

bool check(double x)
{
	double d=a;	  //剩余还付 
	for(int i=0;i<c;i++)	//模拟还款过程 
		d=d-b+d*x/100;

	if(d<0.0001)	//还完了 
		return true;
	else
		return false;
}

int main()
{
	cin>>a>>b>>c;	
	double l=0,r=1000;	//若过不了,r可以再开大一点
	
	while(r-l>0.0001){    //确定精度
		double mid=(l+r)/2;
		if(check(mid))
			l=mid;
		else
			r=mid;	
	}
	printf("%.1f",l);
	
	return 0;
}

8、P1024 [NOIP2001 提高组] 一元三次方程求解

因为区间很大,所以可以二分。
三个答案都在[-100,100]范围内,两个根的差的绝对值>=1,保证了每一个大小为1的区间里至多有1个解,也就是说当区间的两个端点的函数值异号时区间内一定有一个解,同号时一定没有解。/那么我们可以枚举互相不重叠的每一个长度为1的区间,在区间内进行二分查找。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

double a,b,c,d;

double fc(double x)
{
    return a*x*x*x+b*x*x+c*x+d;
}

int main()
{
    double l,r,m,y1,y2;
    int cnt=0;
    scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
    
    for (int i=-100;i<100;i++)
    {
        l=i; 
        r=i+1;
        y1=fc(l); 
        y2=fc(r);
        
        if (cnt==3) break;
            
        if(y1==0)     //判断左端点,是零点直接输出。
        {
            printf("%.2lf ",l); 
            cnt++;
        }                    //不能判断右端点,会重复。  
						 
        if(y1*y2<0)                             //区间内有根。
        {
            while(r-l>=0.001)              
            {
                m=(l+r)/2; 
                if(fc(m)*fc(r)<=0) 
                   l=m; 
                else 
                   r=m;
            }
            printf("%.2lf ",l);  
            cnt++;
        }           
    }
    return 0;
}

但这题可以暴力。。

#include <iostream>
using namespace std;

int main()
{
   double a,b,c,d;
   scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
   
   for(double i=-100;i<=100;i+=0.001)
   {
      double j=i+0.001;
      double y1=a*i*i*i+b*i*i+c*i+d;
      double y2=a*j*j*j+b*j*j+c*j+d;
      
      if(y1>=0&&y2<=0||y1<=0&&y2>=0)
         //printf("%.2lf ",i);
         //printf("%.2lf ",j);
         printf("%.2lf ",(i+j)/2);
   }
   return 0;
}

//*********************************利用二分边界求值****************************************//

9、P1102 A-B 数对

 等式变形为A=B+C,C已知,枚举B即可得到A值,求出两次的边界间有多少值=A即可。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=200010;
int n,c,q[N];
long long cnt;

int main()
{
	cin>>n>>c;
	for(int i=0;i<n;i++)
		cin>>q[i];
	sort(q,q+n);
	
	for(int b=0;b<n-1;b++){
		int a=q[b]+c;	//枚举B,由 C得 A,
		
		int l=-1,r=n;		//再二分查找A在数组中的位置, 
		while(l+1<r){
			int mid=(l+r)/2;
			if(q[mid]<=a)
				l=mid;
			else
				r=mid;
		}
		if(q[l]==a) cnt+=l+1;		//再计算A出现了多少次 :l1-r2+1 
		else continue;
		
		l=-1,r=n;
		while(l+1<r){
			int mid=(l+r)/2;
			if(q[mid]<a)
				l=mid;
			else
				r=mid;
		}
		cnt-=r;//想一想为什么不用判断 
	}
	cout<<cnt;
	
	return 0;
}

10、P1678 烦恼的高考志愿

 枚举学生分数,用二分将离学生分数最近的学校分数(边界)找出。

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;

const int N=100010;

int main()
{
	int m,n;
	long long sum=0;
	int xx[N],xs[N];
	
	scanf("%d%d",&m,&n);
	for(int i=0;i<m;i++) scanf("%d",&xx[i]);
	sort(xx,xx+m);
	for(int i=0;i<n;i++) scanf("%d",&xs[i]);
	
	for(int i=0;i<n;i++){
		int l=0,r=m-1;
		while(l+1<r){
			int mid=(l+r)/2;
			if(xx[mid]<xs[i]) l=mid;
			else r=mid;
		}
		int minn=min(abs(xs[i]-xx[l]),abs(xx[r]-xs[i]));
		sum+=minn;
	}
	printf("%lld",sum);
	
	return 0; 
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值