[二分]JoyOI 收入计划 (tyvj 1359) 二分答案法

题目链接
本来是tvyj1359的题目,但是现在这个oj迁移到JoyOI上了
是夏令营里讲二分答案的一道例题,就当做是二分答案的模板用吧;
题目意思是这样的,现在给一个序列,现在要把这个序列分成m段,然后每一段都有一个和,要求这m段中的和的最大值最小;
就是一个最大值最小化的问题;
对于二分答案的题,需要满足几个前提:
1.答案的范围容易知道;
2.答案区间上是单调的;
3.容易判断某一个答案是否正确;

对于这题:
  1. 答案的范围显然可以固定到[max,sum],max是序列中的最大值,sum是序列的和;
  2. 其次,答案的区间上显然是单调的,我们考虑,假设现在有一个x,能够满足,m段中的和的最大值不超过x,那么也一定可以满足m段中的和的最大值不超过x+1,所以答案一定是单调的;
  3. 要去判断能否找到一个x,使得m段中的和的最大值不超过x也是简单的,我们可以用贪心的办法来找,比如对于序列1,3,2,12,6 要判断m段中的和的最大值是否不超过15也是简单的,比如我们可以分成[1,3,2][12][6]这三组,这样就满足m段中的和的最大值不超过15了;

题目代码如下:

#include <cstdio>
#include <iostream>
using namespace std;
const int maxx=1e5+6;
int n,m,a[maxx];

bool judge(int x){
    int w=0,cnt=1;
    for(int i=1;i<n;i++){
        w+=a[i];
        if(w>x) cnt++,w=a[i];
    }
    return cnt<=m;
}

int main() {
    std::ios::sync_with_stdio(false);
    cin>>n>>m;
    int left=0,right=0,mid;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        if(a[i]>left) left=a[i];
        right+=a[i];
    }
    while(left<right){
        mid=(left+right)>>1;
        if(judge(mid)) right=mid;
        else left=mid+1;
    }
    cout<<left<<endl;
    return 0;
}

然后总结一下二分答案的基本模板:
假设答案区间的范围是[min,max],在答案区间是是单调递增的,如果我们想找到:
最小值最大化:

left=min,right=max;
while(left<right){
    mid=(left+right+1)>>1;
    if(judge(mid)) left=mid;
    else right=mid-1;
}

注意到,mid=(left+right+1)>>1;在这里需要+1是为了避免出现left+1=right的情况,会使mid=left,可能会死循环;
最大值最小化:

left=min,right=max;
while(left<right){
    mid=(left+right)>>1;
    if(judge(mid)) right=mid;
    else left=mid+1;
}

考虑到这两种情况之外,还有个小tip:

对于浮点型运算,不能有+1或者-1的操作;
比如最小值最大化问题中我们可以这样做:

left=min,right=max;
while(left<right){
    mid=(left+right)>>1;
    if(judge(mid)) left=mid;
    else right=mid;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值