当前位置: 代码迷 >> 综合 >> [bzoj1044][DP]木棍分割
  详细解决方案

[bzoj1044][DP]木棍分割

热度:53   发布时间:2023-12-19 05:47:33.0

Description

  有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连 接处,
砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长 度最大的一段长度最小. 并将结果mod
10007。。。

Input

  输入文件第一行有2个数n,m.接下来n行每行一个正整数Li,表示第i根木棍的长度.n<=50000,0<=m<=min(n-1,10
00),1<=Li<=1000.

Output

  输出有2个数, 第一个数是总长度最大的一段的长度最小值, 第二个数是有多少种砍的方法使得满足条件.

Sample Input

3 2
1
1
10

Sample Output

10 2

HINT

两种砍的方法: (1)(1)(10)和(1 1)(10)

题解

第一问二分答案On判定很水吧。。
第二问设一个dp
f[i][j]表示1~i,切了j刀的方案数
方程就是f[i][j]=sigma(f[k][j-1]),满足k+1~i这一段<=第一问的答案
考虑优化
空间上很明显可以看出,f[i][j]只会与f[k][j-1]有关,所以可以滚动一下
时间上,用一个前缀和优化一下,单调队列的思想用一下可以做到nm吧
具体可以看代码可读性很高吧
前缀和千万不要取模。。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
const int mod=10007;
int n,m;
int a[51000];
bool check(int p)
{int tmp=0,len=0;for(int i=1;i<=n;i++){len+=a[i];if(len>p)tmp++,len=a[i];}if(len>p)return false;if(tmp>m)return false;return true;
}
int f[2][51000];//1~j成一段了 
int sum[51000],fro[51000];
int main()
{scanf("%d%d",&n,&m);int s=0;for(int i=1;i<=n;i++){
   scanf("%d",&a[i]);s+=a[i];fro[i]=fro[i-1]+a[i];}int l=1,r=s,ans;while(l<=r){int mid=(l+r)/2;if(check(mid)){ans=mid;r=mid-1;}else l=mid+1;}printf("%d ",ans);int st=0,ret=0;s=0;for(int i=1;i<=n;i++){s+=a[i];if(s<=ans)f[st][i]=1;else break;}sum[0]=0;for(int i=1;i<=n;i++)sum[i]=(sum[i-1]+f[st][i])%mod;for(int i=1;i<=m;i++){st^=1;memset(f[st],0,sizeof(f[st]));int u=0;for(int j=1;j<=n;j++){while(fro[j]-fro[u]>ans)u++;if(u!=0)f[st][j]=(f[st][j]+sum[j-1]-sum[u-1])%mod;else f[st][j]=(f[st][j]+sum[j-1])%mod;}/*for(int j=1;j<=n;j++){int sum=a[j];for(int k=j-1;k>=0;k--){if(sum>ans)break;f[st][j]=(f[st][j]+f[st^1][k])%mod;sum+=a[k];}}*/sum[0]=0;for(int j=1;j<=n;j++)sum[j]=(sum[j-1]+f[st][j]);ret=(ret+f[st][n])%mod;}printf("%d\n",ret);return 0;
}