有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果mod 10007。。。
【原题】
有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果mod 10007。。。
输入文件第一行有2个数n,m. 接下来n行每行一个正整数Li,表示第i根木棍的长度.
输出有2个数, 第一个数是总长度最大的一段的长度最小值, 第二个数是有多少种砍的方法使得满足条件.
【序言】这道题磨了一个上午。总算过了~~
【分析一】第一问真心的简单。直接二分枚举最大长度的最小值即可。细节注意一下。
bool check(int maxx)
{
int now=0,t=1;
for (int i=1;i<=n;i++)
if (now+a[i]<=maxx) now+=a[i];
else
{
if (a[i]>maxx||t>=m) return false;
t++;now=a[i];
}
return true;
}
int erfen(int l,int r)
{
if (l==r) return l;
int mid=(l+r)/2;
if (check(mid)) return erfen(l,mid);
return erfen(mid+1,r);
}
for (i=2;i<=n;i++)
for (j=2;j<=m;j++)
for (k=1;k<i;k++)
if (sum[i]-sum[k]<=ans) f[i][j]=(f[i][j]+f[k][j-1])%p;
然后惊奇地发现:时间、空间都不符合要求!!我们来一点一点优化。
【优化一*空间】这个相对简单啊。我们通过观察发现,f[i][j]中的j只和j-1有关,所以可以用滚存。为了方便,我们可以把i,j的位置换一下,然后把j的循环提到最上面。下面的滚存代码中,now就是用来滚存的。
for (j=2;j<=m;j++)
{
now^=1;
memset(f[now],0,sizeof(f[now]));
for (i=n;i>1;i--)
for (k=1;k<i;k++)
if (sum[i]-sum[k]<=ans) f[now][i]=(f[now][i]+f[now^1][k])%p;
ans2=(ans2+f[now][n])%p;
}
【优化二*时间】想了很长时间。同样也是观察方程。每次k都要循环一边,是不是很无用啊!试想:若sum[i]-sum[k]<=ans,那么sum[i-1]-sum[k]<=ans(其中i-1>k)(因为sum数组是前缀和,满足单调递增)。因此我们可以用类似于单调队列的方法,开一个后缀数组g。如果sum[i]-sum[k]<=ans,就把计算后的值赋到g[i]中去。在以后每个小于i的数中,若i>k,i就可以加上g数组。(有点说不清楚,具体还是要自己仔细研究)
for (j=2;j<=m;j++)
{
now^=1;
memset(f[now],0,sizeof(f[now]));
memset(g,0,sizeof(g));k=n+1;
for (i=n;i>1;i--)
{
if (i>k) f[now][i]+=(g[k]-g[i])%p;else k=i;
while (k>1&&sum[i]-sum[k-1]<=ans)
{
k--;
f[now][i]=(f[now][i]+f[now^1][k])%p;
g[k]=(g[k+1]+f[now^1][k])%p;
}
}
ans2=(ans2+f[now][n])%p;
}
【小结】加了这些优化之后,bzoj上就过的去了。但是代码写的有点乱啊。真是一道经典的DP优化!
【总的AC代码】
#include<stdio.h>
#include<cstring>
using namespace std;
const int maxn=50000+5;
const int p=10007;
int f[2][maxn],n,m,ans,ans2,sum[maxn],a[maxn],i,j,k,now,g[maxn];
bool check(int maxx)
{
int now=0,t=1;
for (int i=1;i<=n;i++)
if (now+a[i]<=maxx) now+=a[i];
else
{
if (a[i]>maxx||t>=m) return false;
t++;now=a[i];
}
return true;
}
int erfen(int l,int r)
{
if (l==r) return l;
int mid=(l+r)/2;
if (check(mid)) return erfen(l,mid);
return erfen(mid+1,r);
}
int main()
{
scanf("%ld%ld",&n,&m);m++;
for (i=1;i<=n;i++)
{
scanf("%ld",&a[i]);
sum[i]=sum[i-1]+a[i];
}
ans=erfen(0,sum[n]);
printf("%ld ",ans);
for (i=1;i<=n;i++)
if (sum[i]<=ans) f[1][i]=1;else break;
now=1;
for (j=2;j<=m;j++)
{
now^=1;
memset(f[now],0,sizeof(f[now]));
memset(g,0,sizeof(g));k=n+1;
for (i=n;i>1;i--)
{
if (i>k) f[now][i]+=(g[k]-g[i])%p;else k=i;
while (k>1&&sum[i]-sum[k-1]<=ans)
{
k--;
f[now][i]=(f[now][i]+f[now^1][k])%p;
g[k]=(g[k+1]+f[now^1][k])%p;
}
}
ans2=(ans2+f[now][n])%p;
}
printf("%ld",ans2);
scanf("%ld%ld",&n,&m);
return 0;
}