某天,我在写二分的时候遇到了一道贪心题
它光辉的名字是——P1182 数列分段
简单描述题目:给出一串数(n个),将其分为m段,求每段数字和最大值的最小值
因为我就是找的二分题 数据限制 可以得出,为了降低时间复杂度,我们可以用二分法枚举,把枚举出来的数假装当成正确答案,然后分析到底正不正确
//因为主要分析的是贪心,所以l和r的取值不做解释
int l=maxx;//maxx 是那一串数里的最大值
int r=sum;
int mid;
while(l<=r)
{
mid=(l+r)/2;
if(check(mid))//check就是分析mid是不是合法的
l=mid+1;
else
r=mid-1;
}
怎么写check函数呢?
四十五度角忧伤望天
我打开了题解,看到了一个这样的代码(~~其实这是事后我自己写的~~ ):
int check(int mid)
{
int sum=0,t=0;
for(int i=1;i<=n;i++)
{
if(sum+a[i]>mid)
{
sum=a[i];
t++;
}
else sum+=a[i];
}
if(t>=m) return 1;
else return 0;
}
看到这个的蒟蒻本弱一脸懵逼,过后开始兢兢业业的分析代码
哦~~~
这个的意思是
如果这个段落里数据和还没达到假装是答案的大小,我们就塞。
如果塞不进去,就将计数器++,就重新弄下一个段落。
尊敬的题解代码做了什么我搞懂了,但是它做这些是为了什么,我还是不懂
在经历了N长时间的纠结下,我终于简化了问题:
遇到可以加的就加一定是段落数最小的,如果这么分的段落数>=m(要求分的段落数),那就是假装的答案小了,需要l=mid+1,否则就是r=mid-1
下一步就是证明为什么 遇到可以加的就加一定是段落数最小的 了
证明完之后,我发现这是一个非常简单的问题
step1:易知,若想要满足假装是答案的mid真的是答案,如果要分段,第一段一定不在在B右边,如下图
step2:则第二段一定存在,且不在C左边,如下图
step3:以此类推可证明。
就这么证明完了,确实有一丝简单 可是我还想了很久
someone告诉我这是一道经典题,一道就可以看出精髓,然后指出了另一道进阶版贪心经典——P2678 跳石头
简单重述题意:N块石头可以移开M块,只能连着跳,问跳跃距离最小值的最大值
wow~~确实很像的亚子
于是我打开了题解
bool check(int x)//这次只看check吧
{
int sum=0;
int cnt=0;
int last=0;
while(cnt<n+1)
{
cnt++;
if (a[cnt]-a[last]<x) sum++;
else last=cnt;//熟悉的内容扑面而来,简述一下就是如果两块之间的距离小于预设答案就扔掉这块石头
}
return sum<=m;
}