什么时候我们要二分答案?
答:当答案具有单调性时(这不是废话吗emmm)
来看一道最经典的例题:
https://www.luogu.org/problemnew/show/P1182
Problem1:对于给定的一个长度为的正整数数列,现要将其分成段,并要求每段连续,且每段和的最大值最小。
数据范围:
考虑二分答案:
我们假设每段和的最大值为内的某个值,显然答案要求的。
单调性:当越大时,选取的合法的段的长度就越长,需要分成的段的个数就越少。当越小时,选取的合法的段的长度就越短,需要分成的段的个数就越多。
那么假设当前可以确定我们求的每段和的最大值的最小值就在内。取:
1、如果取时,我们可以将原数列划成段,那么根据刚才说的单调性,是合法的,此时时也一定合法(能将将数列划成段),但我们要尽量最小化,所以时一定合法,但由于当前是合法的,故时显然,其答案不会比 时更优,所以答案已不可能在内,故取。
2、若当前时不合法,那么同(1)理,取。
初始化:
核心代码:
while(l+1>1;
flag=check(mid);
if(!flag) l=mid;
if(flag) r=mid;
}
这个代码还是蛮有讲究的,具体表现在为什么while循环的终止条件是?
可能有人会疑惑为什么不去。那我们现在假设为循环的终止条件,当前,此时应取。放在这个题里面说:如果当前检验时无法将数列划分成段,此时应取,这样新的,还和刚才的一样。这时你又检验了一遍时合不合法,(前面说了不合法),然后你又取,如此周而复始,你会惊奇的发现你的代码死循了。
所以,我们设置终止条件为,保证退出循环时。
注意:由于二分的区间内包含的值都有可能是答案,所以在退出循环后对和进行是否合法的检验。
对于这个题来讲,答案越小越好,所以退出while循环后的后续处理这么写:
for(ri i=l;i<=r;i++)
if(check(i)) { ans=i; break; }
check函数怎么写?
因为要求划分的段是连续的,所以我们从开始划分。
bool check(int maxn)
{
int now=0,cnt=1;
for(ri i=1;i<=n;i++)
{
if(a[i]>maxn) return 0;单个元素值>mid时肯定不合法
if(now+a[i]<=maxn) { now+=a[i]; continue; }//此时说明不需要划分新段
now=a[i],cnt++;
}
if(cnt<=m) return 1;//当前x=mid合法时返回1,否则返回0
return 0;
}
代码:
#include
#include
#define ri register int
using namespace std;
const int MAXN=100020;
int n,m,sum,a[MAXN],l,r,mid,flag,ans;
bool check(int maxn)
{
int now=0,cnt=1;
for(ri i=1;i<=n;i++)
{
if(a[i]>maxn) return 0;
if(now+a[i]<=maxn) { now+=a[i]; continue; }
now=a[i],cnt++;
}
if(cnt<=m) return 1;
return 0;
}
int main()
{
scanf("%d%d",&n,&m);
for(ri i=1;i<=n;i++) { scanf("%d",&a[i]); sum+=a[i]; }
l=0,r=sum+1;
while(l+1>1;
flag=check(mid);
if(!flag) l=mid;
if(flag) r=mid;
}
for(ri i=l;i<=r;i++)
if(check(i)) { ans=i; break; }
cout<
Problem2:陶陶在地上丢了A个瓶盖,这A个瓶盖丢在一条直线上,现在他想从这些瓶盖里找出B个(B<=A<=100000),使得距离最近的2个距离最大,他想知道最大可以达到多少。
单调性:选取的瓶盖的最近距离越大,能选取的瓶盖个数就越少。
二分距离最近的瓶盖之间的距离。显然,要求的最近的瓶盖之间的距离越大,满足条件的瓶盖就越少。这便是单调性。
check函数的写法:贪心,从左往右能匹配就匹配。
Code:
#include
#include
#include
#define ri register int
using namespace std;
const int MAXN=100020;
int n,m,a[MAXN],l,r,mid,ans;
bool check(int minn)
{
int cnt=1,now=a[1],flag1=0,flag2=0;
for(ri i=2;i<=n;i++)//从左往右贪心一遍
if(a[i]-now>=minn) cnt++,now=a[i];
if(cnt>=m) flag1=1;
cnt=1,now=a[n];
for(ri i=n-1;i>=1;i--)//从右往左贪心一遍
if(now-a[i]>=minn) cnt++,now=a[i];
if(cnt>=m) flag2=1;
if(flag1||flag2) return 1;
return 0;
}
int main()
{
scanf("%d%d",&n,&m);
for(ri i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+n+1);
l=0,r=a[n]-a[1]+1;
while(l+1>1;
if(check(mid)) l=mid;
else r=mid;
}
for(ri i=r;i>=l;i--)
if(check(i)) { ans=i; break; }
cout<