做题记录2021.8.28 二分答案

今天注意学习二分答案
二分查找是一种在有序序列中高效的查找方式。同样的,我们可以把二分的思路运用到某些答案只有一个值的问题中,这些问题包括:

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.当题目的某一步要在有序序列(或虽然不有序,但排序后不影响答案的序列)中查找值,那么应当使用二分查找。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值