从两道基础二分算法题谈check函数的写法

第一题:愤怒的牛 loj链接

两道题目都是基础二分的模板题,先看第一题,题意为总共有 n n n间牛舍, m m m头牛,要将 m m m头牛安排在 n n n间牛舍,为防止牛互相攻击,使两头牛之间的最小距离最大最大! 最大!(重要的事情说三遍)
以样例来说明,

5 3
1 2 8 4 9

根据上面的样例,首先对牛舍进行排序,可以得到

1 2 4 8 9

可以有以下几种方案(将牛安排在以下编号的牛舍)
1 2 4 此方案牛与牛最小的距离为1
1 4 8 此方案牛与牛最小的距离为3
1 4 9 此方案牛与牛最小的距离为3
2 4 8 此方案牛与牛最小的距离为2
2 4 9 此方案牛与牛最小的距离为2
……
从以上方案可以看出,就是要尽可能的让最小距离最大。再来看看下面的代码

bool chk(int t){
	int sum=a[1],ans=1;
	for(int i=2;i<=n;i++){
		if(sum+t>a[i]) continue;
		else{
			sum=a[i];
			ans++;
		   }
	}
	return ans>=m;
}

通过样例的数据,对照上面的代码不难得到,
t=1的时候,ans=5;
t=2或者t=3都可以满足ans=3;
t>=4的时候,ans=2;
因此,只有当t=2或者t=3才能满足条件(样例共3头牛),显然,t=3才符合题意,那么,怎么确保t尽量取到更大的值呢?
可以这样分析,当ans>m的情况那么一定是二分值太小,而ans<m则是二分值太大,
于是为了让t取到更大的值应该采用if(chk(t)) l=mid,这样求出的二分值更大

while(l<r-1){
  int mid=(l+r)/2;
  if(chk(mid)) r=mid;
  else l=mid;
  }
如果把chk函数里面的返回值改成 return ans<=m,配上 if(chk(t)) r=mid;else l=mid 这样,表面上看没什么错误,实际上最终求出的二分值是更小的,这是不符合题意的。

第二题:数列分段 loj链接

本题要求将n个整数系列分成指定的m段,每段至少包含一个数,求处所有的分段方法中,各段的和的最大值最小! 最小! 最小!(重要的事情说三遍)
题目还有一个重要的要求,要求每段的数都是连续的,即按照原数的顺序切割成m段。

5 3
4 2 4 5 1

对照样例数据,可以采用如下集中切法:

4 2 | 4 5 | 1 三段之和分别为 6 9 1 ,和的最大值为9
4 | 2 4 | 5 1 三段之和分别为 4 6 6 ,和的最大值为6

以上两种切割的方法都是分成3段,但是,依据题意,更优的切割方法是第二种.
接下来看看本题正确的check函数的写法

bool chk(int x){
	int sum=0,tot=1;
	for(int i=1;i<=n;i++){
	  if(sum+a[i]<=x) sum+=a[i];
	  else{
	  	tot++;
	  	sum=a[i];
	   }
	}
   return tot<=m;
} 

和第一题一样,考虑到存在tot=m的情况下,其二分值不是最优的(样例中的第1种切割方法),应该尽量让二分值尽可能的小又同时满足tot=m这个前提。
如果tot<=m,说明是二分值偏大,因此可以采用 if(chk(x)) r=mid;else l=mid,这样就可以满足tot=m的同时,二分值最小。

while(l<r-1){
   	  int mid=(l+r)/2;
   	  if(chk(mid))r=mid;
   	  else l=mid;
   }

需要注意的是,本题的二分算法的初值必须让 l = m a x ( a i ) l=max(ai) l=max(ai), r = s u m ( a i ) r=sum(ai) r=sum(ai)

如果把chk函数里面的返回值改成 return ans>=m,配上 if(chk(t)) l=mid;else r=mid 这样,表面上看没什么错误,实际上最终求出的二分值虽然满足了分成了m段,但是二分值却是更大的,(第一种切割方法就属于这种情况)这是不符合题意的。

第一题完整代码

#include <bits/stdc++.h>
using namespace std;
int n,m,a[100008]; 
bool chk(int t){
	int g=a[1],ans=1;
	for(int i=2;i<=n;i++){
		if(g+t>a[i]) continue;
		else{
			g=a[i];
			ans++;
		   }
	}
	return ans>=m;
}
int main()
 {
   scanf("%d%d",&n,&m);
   for(int i=1;i<=n;i++) scanf("%d",a+i);
   sort(a+1,a+n+1);
   int l=a[1],r=a[n];
   while(l<r-1){
   	 int midl=(l+r)>>1;
   	 if(chk(midl)) l=midl;
   	 else r=midl;
   }
   if(chk(l)) printf("%d",l);
   else printf("%d",r);
   return 0;
 }

第二题完整代码

#include <bits/stdc++.h>
using namespace std;
int n,m,a[100005],maxx=-1,cnt;
bool chk(int x){
	int sum=0,tot=1;
	for(int i=1;i<=n;i++){
	  if(sum+a[i]<=x) sum+=a[i];
	  else{
	  	tot++;
	  	sum=a[i];
	   }
	}
   return tot<=m;
} 
int main()
 {
   scanf("%d%d",&n,&m);
   for(int i=1;i<=n;i++){
   	 scanf("%d",a+i);
   	 maxx=max(maxx,a[i]);
   	 cnt+=a[i];
   }
   int l=maxx,r=cnt;
   while(l<r-1){
   	  int mid=(l+r)/2;
   	  if(chk(mid))r=mid;
   	  else l=mid;
   }
   if(chk(l)) printf("%d",l);
   else printf("%d",r);
   return 0;
 }
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值