题目描述
对于给定的一个长度为N的正整数数列 A_{1\sim N}A1∼N,现要将其分成 MM(M\leq NM≤N)段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列 4\ 2\ 4\ 5\ 14 2 4 5 1 要分成 33 段。
将其如下分段:
[4\ 2][4\ 5][1][4 2][4 5][1]
第一段和为 66,第 22 段和为 99,第 33 段和为 11,和最大值为 99。
将其如下分段:
[4][2\ 4][5\ 1][4][2 4][5 1]
第一段和为 44,第 22 段和为 66,第 33 段和为 66,和最大值为 66。
并且无论如何分段,最大值不会小于 66。
所以可以得到要将数列 4\ 2\ 4\ 5\ 14 2 4 5 1 要分成 33 段,每段和的最大值最小为 66。
输入格式
第 11 行包含两个正整数 N,MN,M。
第 22 行包含 NN 个空格隔开的非负整数 A_iAi,含义如题目所述。
输出格式
一个正整数,即每段和最大值最小为多少。
输入输出样例
输入 #1复制
5 3 4 2 4 5 1
输出 #1复制
6
说明/提示
对于 20\%20% 的数据,N\leq 10N≤10。
对于 40\%40% 的数据,N\leq 1000N≤1000。
对于 100\%100% 的数据,1\leq N\leq 10^51≤N≤105,M\leq NM≤N,A_i < 10^8Ai<108, 答案不超过 10^9109。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int mas=1e5+5;
ll r;
int v[mas],l;
int N,M;
//最终结果是p,下于p的不成立,大于p的成立,所以二分的区间基本确定
//接下来确定l r;
//最终结果是res,区间和的最大值最小值是res,如果是res-x,那么结果的分组一定的>n的
bool check(int m);
int main()
{
scanf("%d%d",&N,&M);
for(int i=1;i<=N;i++)
{
scanf("%d",&v[i]);
l=max(l,v[i]);
r+=v[i];
}
while(l<r)
{
int mid=l+r>>1;//此时的l 和 r不用加一减一
if(check(mid))r=mid;
else l=mid+1;
// cout<<l<<" "<<r<<endl;
}
cout<<r;
return 0;
}
bool check(int m)//在check的时候进行准换思路,,在小于m的情况下看需要的的是几个分组
//如果分组大于了对应的N,就不可能(此时的和,或者剩余的空间容不下了此时的数值,就重新开一个)
{
int cnt=1,res=m;//剩余的空间,初始的数值1,因为最终的结果是肯定会有一个多出来的
for(int i=1;i<=N;i++)
if(res>=v[i])res-=v[i];
else res=m-v[i],cnt++;
// cout<<cnt<<endl;
return M>=cnt;
}
总结:
利用二分法作为判断条件进行check(),前提是含有单调性。寻找到单调区间
首先问什么假设什么,假设这个值 是res,凡是大于res的数值(每个区间和都可以大于这个上限)一定可以,小于res的数值(每个区间的和小于res这个上限)一定不可以。
然而res的数值可以的取值范围是l=max(v[i]),r+=v[i]
如何判断每个区间和小于mid的情况下的,区间的最小分块是否大于M,采用贪心的策略,直接可以证明。
注意:
cnt 的初值是1,因为最终会有一个剩余的一部分没有加入;
在check()中求cnt的写法很美,可以多多的吸收。