BZOJ1044: [HAOI2008]木棍分割 (二分 + DP)
题意:n根木棍依次连在一起 最多切m个端点 使得最长的一段最小
在保证最长的最小的情况下 有多少种不同的切法
题解:第一问傻子都知道二分
第二问想了一会不会做 但其实就是很简单的dp
dp[i][j]表示切完第i刀切到第j段后面的的种数
想了一下觉得空间过不去 然而这个是可以滚动的 因为每一刀的转移都只和前一刀有关系 再优化下时间
预处理一下last[i]表示在第i个木棍后面切的话 至少要在last[i]这个点及以后也切一下
跃然纸上的转移 dp[i][j] += dp[i - 1][k] if(last[j] <= k < j) 但这是个接近n^3的复杂度
然后发现每次转移 dp[i][j]都是加的相同一段 所以我们可以每次前缀和搞一下
#include <bits/stdc++.h> using namespace std; const int mod = 10007; int n, m; int q[50005]; int sum[50005]; int last[50005]; int dp[2][50005]; int pre[2][50005]; int ans1, ans2; int check(int x) { int res = 0; int cnt = 0; for(int i = 1; i <= n; i++) { res += q[i]; if(res > x) cnt++, res = q[i]; } return cnt; } int main() { ans2 = 0; scanf("%d%d", &n, &m); int l = 0, r = 0; for(int i = 1; i <= n; i++) { scanf("%d", &q[i]); l = max(l, q[i]); r += q[i]; } int mid = l + r >> 1; while(l + 1 < r) { mid = l + r >> 1; if(check(mid) <= m) ans1 = mid, r = mid; else l = mid; } if(check(l) <= m) ans1 = l; int zsum = 0; int p = 1; for(int i = 1; i <= n; i++) { zsum += q[i]; while(zsum > ans1) zsum -= q[p], p++; last[i] = p - 1; if(last[i] == 0) dp[1][i] = 1; } ans2 += dp[1][n]; for(int i = 1; i <= n; i++) pre[1][i] = (dp[1][i] + pre[1][i - 1]) % mod; for(int i = 2; i <= m + 1; i++) { memset(dp[i & 1], 0, sizeof(dp[i & 1])); pre[i & 1][i - 1] = 0; for(int j = i; j <= n; j++) { dp[i & 1][j] = (dp[i & 1][j] + pre[(i - 1) & 1][j - 1]) % mod; if(last[j]) dp[i & 1][j] = (dp[i & 1][j] - pre[(i - 1) & 1][last[j] - 1]) % mod; dp[i & 1][j] = (dp[i & 1][j] + mod) % mod; } for(int j = i; j <= n; j++) pre[i & 1][j] = (pre[i & 1][j - 1] + dp[i & 1][j]) % mod; ans2 = (ans2 + dp[i & 1][n]) % mod; } printf("%d %d\n", ans1, ans2); return 0; }