两种砍的方法: (1)(1)(10)和(1 1)(10)
题解
人生第一道二分答案成就感满满!二分答案,是指在最小值和最大值之间二分枚举答案,逐渐把答案缩小到一个值得出最优解。二分答案的题目设问常常是最大值最小、最小值最大,解的可能性应该是单调的(即某值不可行则答案只能比它大或只能比它小),需要能简单地验证答案正确与否,要注意的就是防止死循环(不过这道题好像没有这个问题)。对于木棍分割,如果这个值可行,最优解可能比它更小,以它为上界看一看有没有更小的解;如果这个值不可行,只能舍弃它和比它更小的解,以它+1为下界搜索答案。
int check(long long x)
{
temp=he=0;
for(int i=1;i<=n;i++)
{
if(he+li[i]<=x)
he+=li[i];
else
{
he=li[i];
temp++;
if(temp>m)
return 0;
}
}
if(temp>m) return 0;
return 1;
}
le=yd,jg=zd;(最小解是单段木块长度的最大值,最大解是所有木块长度之和)
while(le
{
mid=(le+jg)>>1;
if(check(mid)) jg=mid;
else le=mid+1;
}
dp部分优化空间复杂度用滚动数组,优化时间复杂度用类似单调队列的思路,和Watching firework is fun大同小异,最后结果为f[i][n]之和。f[i][j]表示j段木块切i次的方案,f[i][j]为所有满足sum[i]-sum[k]<=jg的f[k][j-1]之和(1<=k
UPD:关于这个二分卡死的问题后来慢慢弄清了是怎么回事。并不是+1或不+1就容易被卡死,而是你要保证每次l和r总有一个是变化的。比如说mid=l+r>>1,那mid可能和l相等,你就不能if(……)l=mid,如果必须这样就得mid=(l+r+1)>>1。OI中的很多东西开始不明白,用了很长时间忽然就搞懂了,只有看清了内部的原理才是真正的通透啊。
#include
#include
#include
using namespace std;
const int sj=;
int li[sj],mod=,yd;
int la,no,head,n,m,mid,temp,tail;
long long s[sj],f[][sj],zd,jg,le,he,fas;
int dbj(int x,int y)
{
return x>y?x:y;
}
int xbj(int x,int y)
{
return x
}
void init()
{
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++)
{
scanf("%d",&li[i]);
s[i]=s[i-]+li[i];
zd+=li[i];
yd=dbj(li[i],yd);
}
}
void dp()
{
for(int i=;i<=n;i++)
{
if(s[i]<=jg)
f[][i]=;
if(s[i]>jg)
break;
}
fas=f[][n];
for(int j=;j<=m;j++)
{
head=;
tail=;
he=;
la=(j+)%;
no=j%;
for(int i=;i<=n;i++)
{
if(i
for(int k=tail+;k
he+=f[la][k];
tail=i-;
while(s[i]-s[head]>jg)
{
he-=f[la][head];
head++;
}
f[no][i]=he;
f[no][i]%=mod;
}
fas+=f[no][n];
fas%=mod;
}
}
int check(long long x)
{
temp=he=;
for(int i=;i<=n;i++)
{
if(he+li[i]<=x)
he+=li[i];
else
{
he=li[i];
temp++;
if(temp>m)
return ;
}
}
if(temp>m) return ;
return ;
}
int main()
{
init();
le=yd,jg=zd;
while(le
{
mid=(le+jg)>>;
if(check(mid)) jg=mid;
else le=mid+;
}
printf("%lld\n",jg);
dp();
printf("%lld",fas);
//while(1);
return ;
}