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
1
1
10
Sample Output
10 2
两种砍的方法: (1)(1)(10)和(1 1)(10)
数据范围
n<=50000, 0<=m<=min(n-1,1000).
1<=Li<=1000.
两种砍的方法: (1)(1)(10)和(1 1)(10)
数据范围
n<=50000, 0<=m<=min(n-1,1000).
1<=Li<=1000.
一开始以为是水题……结果斯巴达了三个小时才调出来……
第一问是求最小值,而且最大值最小,显然二分。就是枚举木棍的总长度最大值,然后判定。效率O(nlog(max(sum[n]))),其中sum[i]为前缀和。
第二问超坑……设f[i][j]为分成i段,枚举前j根木棍,每段长度都不大于第一问ans的方案数。f[i][j]=Σf[i-1][k],其中k满足sum[k]-s[j-1]<=ans,即最后一段区间总长(j)不超过最大值。这样暴力枚举效率O(n^2m)会T。但是发现转移的Σf[i-1][k]可以通过维护一个队列保存当前的k来O(1)算出来。然后又发现直接开5千万的数组要做死,还要用滚动数组……
#include<cstdio> #include<cstring> #define maxn 100001 #define mod 10007 inline int read() { int x=0;char ch=getchar(); while(ch<'0'||ch>'9')ch=getchar(); while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x; } int n,m; int a[maxn]; int s[maxn]; int q[maxn]; int f[2][maxn]; int l,r,ans,ans2; inline bool mark(int k) { int sum,piece=m,now=1; while (piece&&now<=n) { sum=0; while (now<=n&&sum+a[now]<=k) sum+=a[now++]; piece--; } if (now<=n&&s[n]-s[now-1]>k) return 0; return 1; } inline void init() { n=read(); m=read(); for (int i=1;i<=n;i++) { a[i]=read(); s[i]=s[i-1]+a[i]; if (a[i]>l) l=a[i]; } r=s[n]; } inline void bsearch( { while (l<=r) { int mid=(l+r)>>1; if (mark(mid)){ans=mid;r=mid-1;} else l=mid+1; } printf("%d ",ans); } inline void dp() { f[0][0]=1; int pre,cur,res; for (int i=1;i<=m;i++) { pre=i&1; cur=pre^1; int h=1,t=1; q[1]=0;res=f[cur][0]; for (int j=1;j<=n;j++) { while (h<=t&&s[j]-s[q[h]]>ans) { res=(res-f[cur][q[h++]])%mod; res=(res+mod)%mod; } f[pre][j]=res; q[++t]=j;res=(res+f[cur][j])%mod; } for (int j=n-1;j>0;j--) { if (s[n]-s[j]>ans) break; ans2=(ans2+f[pre][j])%mod; } memset(f[cur],0,sizeof f[cur]); } printf("%d\n",ans2); } int main() { init(); bsearch(); dp(); }
——by zhber,转载请注明来源