Description
有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果mod 10007。。。
Input
输入文件第一行有2个数n,m. 接下来n行每行一个正整数Li,表示第i根木棍的长度.
Output
输出有2个数, 第一个数是总长度最大的一段的长度最小值, 第二个数是有多少种砍的方法使得满足条件.
Sample Input
3 2
1
1
10
Sample Output
10 2
两种砍的方法: (1)(1)(10)和(1 1)(10)
数据范围
n<=50000, 0<=m<=min(n-1,1000).
1<=Li<=1000.
题解
【序言】这道题磨了一个上午。总算过了~~
【分析一】第一问真心的简单。直接二分枚举最大长度的最小值即可。细节注意一下。
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);
}
【分析二】第二问很容易看出是DP,而且方程也很简单。设f[i][j]表示到第i个点,截取j个木棍且满足要求的方案数。为了加快速度,我们开一个前缀和的sum优化,那么i–j的长度就是sum[j]-sum[i-1]。
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;
}
其中的now^1就是0和1的变换。这样,就不用开50000*50000的数组了!空间过得去了。
【优化二*时间】想了很长时间。同样也是观察方程。每次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优化!
以下非转载
然后是我的代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 50010
#define mod 10007
int n,m,a[N],sum[N],f[2][N],g[N],now,ans2;
bool test(int k)
{
int now = 0,t = 1;
for(int i = 1;i <= n;i++)
if(now + a[i] <= k)now += a[i];
else
{
if(a[i] > k || t >= m) return false;
t++; now = a[i];
}
return true;
}
int main()
{
scanf("%d%d",&n,&m);
m++; sum[0] = 0;
for(int i = 1;i <= n;i++)
{
scanf("%d",&a[i]);
sum[i] = sum[i-1]+a[i];
}
int ans,L = 0,R = sum[n];
while(L <= R)
{
int mid = (L+R)>>1;
if(test(mid)) {R = mid-1;ans = mid;}
else L = mid+1;
}
printf("%d ",ans);
for(int i = 1;i <= n;i++)
if(sum[i] <= ans) f[1][i] = 1; else break;
now = 1;
for(int j = 2;j <= m;j++)
{
now ^= 1;
memset(f[now],0,sizeof(f[now]));
memset(g,0,sizeof(g));
int k = n+1;
for(int i = n;i > 1;i--)
{
if(i > k) f[now][i] += (g[k]-g[i])%mod; else k = i;
while(k > 1 && sum[i] - sum[k-1] <= ans)
{
k--;
f[now][i] = (f[now][i]+f[now^1][k]) % mod;
g[k] = (g[k+1] + f[now^1][k]) % mod;
}
}
ans2 = (ans2 + f[now][n]) % mod;
}
printf("%d",ans2);
return 0;
}