今天注意学习二分答案
二分查找是一种在有序序列中高效的查找方式。同样的,我们可以把二分的思路运用到某些答案只有一个值的问题中,这些问题包括:
1.假定一个解并判定是否可行(尤其是答案为浮点数的题目中)
如果答案为浮点数,并且找不到离散化思路的话,那么可能无法直接搜索或使用其他方法求出答案。此时可以采取二分答案。
例:
这题可以用二分答案,不过由于浮点数的误差问题,不能像一般的二分那样将边界设为l<r或l<=r。可以采取固定的、足够多(不能太多)的循环次数(如100)或r-l小于一个足够小(不能太小)的值eps。代码如下:
#include <cstdio>
#include <algorithm>
const int M=10001,MAX_LEN=100001;
const double eps=1e-11;
using namespace std;
double a[M];
int n,k;
inline int cut(double len) {
int res=0;
for(int i=0; i<n; i++) {
res+=(int)(a[i]/len);
}
return res;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=0; i<n; i++) {
scanf("%lf",&a[i]);
}
double l=0,r=*max_element(a,a+n));
while(r-l>=eps) {
double mid=(l+r)/2.0;
if(cut(mid)>=k) l=mid;
else r=mid;
}
printf("%.2lf",(int)(r*100)/100.0);
return 0;
}
值得注意的是,最后答案应当选用r而非l,因为本题求的是最大值。
若长度全部为k(常数),如果选用l为最终答案那么就会得到错误答案k-0.01
2.最大化最小值(或相反)
例:
类似问题用二分可以高效解决。
假设一个答案k,很明显,如果在这个“答案”下能“安排”所有牛,那么就在右区间内继续查找可能的更大的答案,否则就不得不在左区间内查找。
下面考虑如何判断是否能“安排”所有牛:对于每一个起始位置l,每次贪心地选取l右边第一个r使x[r]>=x[l]+k。这一过程同样可以通过对排序后的数组使用二分查找来解决。如此直到被“安排”的牛的数目达到c即可。
代码如下:
#include <cstdio>
#include <algorithm>
const int M=100001;
using namespace std;
int m,n,a[M];
inline bool check(const int x) {
int cnt=1,cur=0;
while(cur<n) {
int next=lower_bound(a+cur,a+n,a[cur]+x)-a; //找到右边第一个>=a[cur]+x的点的位置
if(next<n) cnt++;
if(cnt==m) return 1; //提前退出
cur=next;
}
return cnt==m;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0; i<n; i++) scanf("%d",&a[i]);
sort(a,a+n);
int l=0,r=a[n-1]-a[0]+1;
while(l<r) {
int mid=l+(r-l)/2;
if(check(mid)) l=mid+1;
else r=mid;
}
printf("%d",l-1); //注意最后l还加了1
return 0;
}
假设真实答案为TrueAns,贪心地对数列进行分段,那么如果假设的答案K使得数列被分成的段数<=m,就说明K>=TrueAns,否则K<TrueAns。显然,K<max{
a
1
,
a
2
,
.
.
.
,
a
n
a_1,a_2,... ,a_n
a1,a2,...,an}时必有K<TrueAns。
#include <cstdio>
#include <algorithm>
#define long long long
const int M=100001;
using namespace std;
int a[M];
int n,m,maxn;
bool check(long k) {
if(k<maxn) return 0; //显然和必须不小于数列中的最大值
long Sum=0,maxsum=0;
int sec=0,now=0;
while(now<n) {
Sum=0;
while(Sum+a[now]<=k&&now<n) {
Sum+=(long)a[now++];
}
maxsum=max(maxsum,Sum);
sec++;
}
return sec<=m;
}
int main() {
long l=0,r;
scanf("%d%d",&n,&m);
for(int i=0; i<n; i++) {
scanf("%d",&a[i]);
r+=(long)a[i];
maxn=max(maxn,a[i]);
}
while(l<r) {
int mid=l+(r-l)/2;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%lld",l);
return 0;
}
总结:
1.二分答案通常要有一个check函数。要仔细考虑check返回true或false的各种情况。
2.编写check函数时很可能要用到贪心思路。
3.二分答案和二分查找一样,都要注意对边界的处理。
4.当题目的某一步要在有序序列(或虽然不有序,但排序后不影响答案的序列)中查找值,那么应当使用二分查找。