BZOJ 1044 木棍分割 解题报告(二分+DP)

来到机房刷了一道水(bian’tai)题。题目思想非常简单易懂(我的做法实际上参考了Evensgn 范学长,在此多谢范学长了)

题目摆上:

1044: [HAOI2008]木棍分割

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 3162  Solved: 1182
[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)

多谢范学长的博客教会了我这道DP的优化。

接下来说说这道题的思路:

首先我们需要求被分割后的最长的木条的长度,这个很简单,二分+贪心check即可,跟基本的套路一样,相信大家都能理解:

bool check(int x/*x表示我们二分的最长段的长度*/){
    if(x < p)return false;
    int cut = 0,add = 0;
    for(int i = 1;i <= n;++i){
        if(add + a[i] > x){
            cut++;//cut表示当前已经分割了几次
            if(cut > m)return false;
            add = 0;
        }
        add += a[i];
    }
    return true;
}

while(l <= r){
    mid = (l + r) >> 1;
    if(check(mid))r = mid - 1;
    else l = mid + 1;
}

接下来求完了我们需要的len,就应该求有多少种方案可以满足len了。

众所周知,动态规划的第一步是要写出状态……然后再来搞

我们设f[i][j]表示前i段一共分割了j次,设ss[i]为a[i]的前缀和,然后写出dp方程:

f[i][j] = Σf[k][j-1] 其中k要满足的条件是(1 <= k < i) && (ss[i] - ss[k] <= len)(这是很容易从题目中得出的)。

于是我们就可以完成了。

但是这样也太简单了吧……毕竟是HAOI的题目,如果这么简单就是NOIP难度了(虽然本人不否认以前的省选题目也有NOIP难度的)

然后注意到数据范围:n<=50000,0<=m<=min(n-1,1000)

我们注意到我们程序的时间复杂度实际上是O(n^2 m) 的,这明显就是爆了时间的。

那然后该怎么办呢?

我们可以注意到,如果我们设sumf 表示枚举到k的时候Σf[k][j-1],(1 <= k < i) && (ss[i] - ss[k] <= len),mink表示满足(1 <= k < i) && (ss[i] - ss[k] <= len)的最小的k。

其实对于 f[i][Now] ,其实是 f[mink][Last]...f[i-1][Last] 这一段 f[k][Last] 的和,mink 是满足 Sum[i] - Sum[k] <= Len 的最小的 k ,对于从 1 到 n 枚举的 i ,相对应的 mink 也一定是非递减的(因为 Sum[i] 是递增的)。我们记录下 f[1][Last]...f[i-1][Last] 的和 Sumf ,mink 初始设为 1,每次对于 i 将 mink 向后推移,推移的同时将被舍弃的 p 对应的 f[p][Last] 从 Sumf 中减去。那么 f[i][Now] 就是 Sumf 的值。(此段复制自Evensgn的博客,因为我觉得自己可能写不出来这么详细)

这样我们就不必枚举k,时间复杂度就降低到可以接受的O(nm)了。

但是这样就完成了?别天真了,还有一个坑那,时间解决了,空间呢?我们的空间复杂度是O(nm)啊,用计算器算一下明显超了。

这时候的DP有一个技巧(类似于飞扬的小鸟NOIP2014),我们发现其实j所属的那一维,只能由j-1转移而来,所以可以使用最常用的手段——滚动数组,来滚动掉第二维

使用now和pre,f[maxn][2],now和pre只能为0或1,且pre = now^1,每完成一遍外层m循环更新now ^= 1,pre = now^1。

这样子我们的空间复杂度也降到可以接受的O(n)辣!

终于完成了,接下来就是代码了:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <cmath>
 5 #include <cstdlib>
 6 #include <algorithm>
 7 using namespace std;
 8 const int maxn = 50005;
 9 const int maxm = 1005;
10 const int mod = 10007;
11 int get_num(){
12     int num = 0;
13     char c;
14     bool flag = false;
15     while((c = getchar()) == ‘ ‘ || c == ‘\r‘ || c == ‘\n‘);
16     if(c == ‘-‘)
17         flag = true;
18     else num = c - ‘0‘;
19     while(isdigit(c = getchar()))
20         num = num * 10 + c - ‘0‘;
21     return (flag ? -1 : 1)*num;
22 }
23 int n,m;
24 int a[maxn],ss[maxn];
25 int f[maxn][2];
26 int now,pre,len,p = 0,mid,ans = 0;
27 bool check(int x){
28     if(x < p)return false;
29     int cut = 0,add = 0;
30     for(int i = 1;i <= n;++i){
31         if(add + a[i] > x){
32             cut++;
33             if(cut > m)return false;
34             add = 0;
35         }
36         add += a[i];
37     }
38     return true;
39 }
40 int main(){
41     memset(f,0,sizeof(f));
42     memset(a,0,sizeof(a));
43     memset(ss,0,sizeof(ss));
44     n = get_num();
45     m = get_num();
46     for(int i = 1;i <= n;++i){
47         a[i] = get_num();
48         ss[i] = a[i] + ss[i-1];
49         p = max(p,a[i]);
50     }
51     int l = 0,r = 50000000;
52     while(l <= r){
53         mid = (l + r) >> 1;
54         if(check(mid))r = mid - 1;
55         else l = mid + 1;
56     }
57     len = r + 1;
58     now = 0;
59     pre = now^1;
60     int sumf = 0;
61     int mink = 0;
62     for(int i = 0;i <= m;++i){
63         sumf = 0;
64         mink = 1;
65         for(int j = 1;j <= n;++j){
66             if(i == 0)
67                 if(ss[j] <= len)f[j][now] = 1;
68                 else f[j][now] = 0;
69             else{
70                 while(mink < j && ss[j] - ss[mink] > len){
71                     sumf -= f[mink][pre];
72                     sumf = (sumf + mod) % mod;
73                     mink++;
74                 }
75                 f[j][now] = sumf;
76             }
77             sumf += f[j][pre];
78             sumf %= mod;
79         }
80         ans += f[n][now];
81         ans %= mod;
82         now ^= 1;
83         pre = now ^ 1;
84     }
85     printf("%d %d\n",len,ans);
86     return 0;
87 }

附赠一张图片:

时间: 2024-10-01 04:50:34

BZOJ 1044 木棍分割 解题报告(二分+DP)的相关文章

[HAOI2008]木棍分割解题报告

305 . [HAOI2008] 木棍分割 ★★★☆ 输入文件:stick.in 输出文件:stick.out 简单对比 时间限制:3 s 内存限制:64 MB [问题描述] 有n根木棍,第i根木棍的长度为Li,n根木棍依次连结在一起,总共有n-1个连接处.现在允许你最多砍断m个连接处,砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小,并且输出有多少种砍木棍的方法使得总长度最大的一段长度最小. [输入格式] 输入文件第一行有2个数n,m 接下来n行每行一个正整数Li,表示第i根木棍

BZOJ 1044 木棍分割

Description 有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果mod 10007... Input 输入文件第一行有2个数n,m. 接下来n行每行一个正整数Li,表示第i根木棍的长度. Output 输出有2个数, 第一个数是总长度最大的一段的长度最小值, 第二个数是有多少种砍的方法使得

bzoj1044[HAOI2008]木棍分割 单调队列优化dp

1044: [HAOI2008]木棍分割 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 4314  Solved: 1664[Submit][Status][Discuss] Description 有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果

bzoj1044 [HAOI2008]木棍分割——前缀和优化DP

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1044 咳咳...终于A了... 居然没注意到正着找pos是n方会TLE...所以要倒着找pos: 二分还写错了,改了半天... 小心前缀和取模后相减变成负数!!!!!!!!! 代码如下: #include<iostream> #include<cstdio> #include<cstring> using namespace std; int const maxn

BZOJ 4341 [CF253 Printer] 解题报告

乍一看这个题好像可以二分优先度搞搞... 实际上能不能这么搞呢...? 我反正不会... 于是开始讲我的乱搞算法: 首先肯定要把任务按照优先度排序. 用一棵在线建点的线段树维护一个时刻是否在工作. 然后就依次插入任务,记为 i,具体而言就是二分其右端点,然后令这整个区间都变成 “工作” 的状态. 在 i 被插入之前,还要检验一下在当前情况那个神秘任务的右端点是不是题中所要求的那个. 如果是,并且 i-1 的优先度和 i 的优先度不相邻或者 i 就是最优先的任务,那么就令那个神秘任务的优先度为 i

ACM : HDU 2899 Strange fuction 解题报告 -二分、三分

Strange fuction Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 5933 Accepted Submission(s): 4194 Problem Description Now, here is a fuction: F(x) = 6 * x^7+8*x^6+7*x^3+5*x^2-y*x (0 <= x <=100) C

ACM:HDU 2199 Can you solve this equation? 解题报告 -二分、三分

Can you solve this equation? Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 16281 Accepted Submission(s): 7206 Problem Description Now,given the equation 8*x^4 + 7*x^3 + 2*x^2 + 3*x + 6 == Y,can

BZOJ 3969 Low Power 解题报告

我们首先将所有电池排序,那么我们可以找到一组最优方案,使得一台机器的能量之差是相邻两电池的能量之差. 然后我们就二分这个答案,从前往后贪心地选这个数对,然后看是否所有的数对都是满足条件的. 假设这个数对是 i - 1, i,并且是第 j 个数对,那么我们称满足条件为: 2nk - i + 2 >= 2k(n - j + 1) 意思就是能拿出足够多的电池来组成机器人. 然后注意特判:如果不能选出足够多的数对就返回 false,我在这里 WA 到死... 毕竟 Gromah 太弱,只会做水题. 1

BZOJ 3971 Матрёшка 解题报告

很自然想到区间 DP. 设 $Dp[i][j]$ 表示把区间 $[i, j]$ 内的套娃合并成一个所需要的代价,那么有: $Dp[i][i] = 0$ $Dp[i][j] = min\{Dp[i][k] + Dp[k + 1][j] + Merge([i, k], [k + 1, j])\} (i \le k < j)$ 于是问题在于算 $Merge([a, b], [c, d])$. 我们考虑一下:区间 $[a, b]$ 内的哪些套娃是需要打开的: 是不是 $[a, b]$ 中所有大于 $[c