1. 程式人生 > >[BZOJ1044][HAOI2008]木棍分割 二分+貪心+dp+前綴和優化

[BZOJ1044][HAOI2008]木棍分割 二分+貪心+dp+前綴和優化

while close 個數 max sub -a 最小值 成了 span

1044: [HAOI2008]木棍分割

Time Limit: 10 Sec Memory Limit: 162 MB Submit: 4112 Solved: 1577 [Submit][Status][Discuss]

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)

先二分出答案,然後設f[i][j]表示第i個點切斷,切斷了j次的方案數,j可以滾動。

用s[i]求出a的前綴和,用p[i]表示第i個位置最左側的可切割點,用add[i]表示f的前綴和。

技術分享
 1 #include<iostream>
 2 #include<cstring>
 3
#include<cstdio> 4 #include<cstdlib> 5 #include<cmath> 6 #include<algorithm> 7 #define mod 10007 8 using namespace std; 9 int n,m; 10 int a[50005]; 11 int sum; 12 bool check(int now) { 13 int t=0,b=0; 14 for(int i=1;i<=n;i++) { 15 if(a[i]>now) return 0; 16 if
(t+a[i]>now) { 17 b++; 18 t=a[i]; 19 } 20 else t+=a[i]; 21 } 22 return b<=m; 23 } 24 int f[50005]; 25 int s[50005]; 26 int add[50005],p[50005]; 27 int main() { 28 scanf("%d%d",&n,&m); 29 for(int i=1;i<=n;i++){scanf("%d",&a[i]); sum+=a[i];s[i]=s[i-1]+a[i];} 30 int l=0,r=sum; 31 while(l<=r) { 32 int mid=(l+r)>>1; 33 if(check(mid)) r=mid-1; 34 else l=mid+1; 35 } 36 int ans1=r+1; 37 printf("%d ",ans1); 38 int ans2=0; 39 for(int i=1;i<=n;i++) if(s[i]<=ans1) add[i]=add[i-1]+1;else add[i]=add[i-1]; 40 if(s[n]<=ans1) ans2++; 41 for(int i=1;i<=n;i++) { 42 int can=s[i]-ans1; 43 int t=lower_bound(s+1,s+i,can)-s; 44 p[i]=t; 45 } 46 for(int j=1;j<=m;j++) { 47 for(int i=1;i<=n;i++) { 48 f[i]=(add[i-1]-add[max(0,p[i]-1)]+mod)%mod; 49 } 50 ans2+=f[n];ans2%=mod; 51 for(int i=1;i<=n;i++) add[i]=(add[i-1]+f[i])%mod; 52 } 53 printf("%d",ans2); 54 }
View Code

[BZOJ1044][HAOI2008]木棍分割 二分+貪心+dp+前綴和優化