BZOJ 1044 木棍分割

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

HINT

 

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

数据范围  

   n<=50000, 0<=m<=min(n-1,1000).

   1<=Li<=1000.

 

Source

本题第一问明显可以二分答案(满足可二分性)——二分答案ans贪心检测。

至于第二问,可以用序列dp来解决。f[i][j]表示前i段木棍,分成j份的合法方案数。转移也很好想,f[i][j]=Σf[k][j-1](pre[i]-pre[k]<=ans),pre代表前缀和。看上去复杂度是O(m*n2)。但其实可以优化一下,因为i↑,所有k的下界也↑。所以我们可以利用单调队列,记录对于f[i][j]所有合法的f[k][j-1]的和,以此进行dp。复杂度O(m*n)。

 

 1 #include<cstdio>
 2 #include<cstdlib>
 3 using namespace std;
 4 
 5 #define rhl 10007
 6 #define maxn 50010
 7 #define maxm 1010
 8 
 9 int n,m,ans,L[maxn],sum[maxn];
10 short f[maxn][maxm],res[maxn];
11 
12 struct node
13 {
14     int a[maxn],*h,*t;
15     node() {h = t = a;}
16     
17     inline void push(int k)
18     {
19         *(++t) = k;
20         for (int i = 1;i <= m;++i)
21             (res[i] += f[k][i-1]) %= rhl;
22     }
23 
24     inline void pop(int k)
25     {
26         while (sum[k]-sum[*(h+1)] > ans)
27         {
28             for (int i = 1;i <= m;++i)
29                 (res[i] -= f[*(h+1)][i-1]) %= rhl;
30             ++h;
31         }
32     }
33 }team;
34 
35 inline bool okay(int l)
36 {
37     int i,j,tot = 0;
38     for (i = 1;i <= n;)
39     {
40         if (tot > m + 1) return false;
41         for (j = i-1;j+1 <= n&&sum[j+1]-sum[i-1] <= l;++j);
42         if (j < i) return false;
43         ++tot; i = j+1;
44     }
45     return tot <= m + 1;
46 }
47 
48 inline void dp()
49 {
50     ++m;
51     f[0][0] = 1;
52     team.push(0);
53     for (int i = 1;i <= n;++i)
54     {
55         team.pop(i);
56         for (int j = 1;j <= m;++j) f[i][j] = res[j];
57         team.push(i);
58     }
59     ans = 0;
60     for (int i = 1;i <= m;++i) (ans += f[n][i])%=rhl;
61     ans = ans % rhl + rhl;
62     ans %= rhl;
63 }
64 
65 int main()
66 {
67     scanf("%d %d",&n,&m);
68     for (int i = 1;i <= n;++i) scanf("%d",L+i),sum[i] = sum[i-1] + L[i];
69     int l = 1,r = sum[n],mid;
70     while (l <= r)
71     {
72         mid = (l + r) >> 1;
73         if (!okay(mid)) l = mid + 1;
74         else r = mid - 1;
75     }
76     printf("%d ",ans = l);
77     dp();
78     printf("%d",ans);
79     return 0;
80 }
View Code

 

posted @ 2015-02-16 10:45  lmxyy  阅读(325)  评论(0编辑  收藏  举报