[BZOJ 1044] [HAOI2008] 木棍分割 【二分 + DP】

题目链接:BZOJ 1044

 

第一问是一个十分显然的二分,贪心Check(),很容易就能求出最小的最大长度 Len 。

第二问求方案总数,使用 DP 求解。

  使用前缀和,令 Sum[i] 为前 i 根木棍的长度和。

  令 f[i][j] 为前 i 根木棍中切 j 刀,并且满足最长长度不超过 j 的方案数,那么:

    状态转移方程: f[i][j] = Σ f[k][j-1]   ((1 <= k <= i-1) &&  (Sum[i] - Sum[k] <= Len))  

  这样的空间复杂度为 O(nm) ,时间复杂度为 O(n^2 m) 。显然都超出了限制。

  下面我们考虑 DP 的优化。

  1) 对于空间的优化。

    这个比较显然,由于当前的 f[][j] 只与 f[][j-1] 有关,所以可以用滚动数组来实现。

    f[i][Now] 代替了 f[i][j] , f[i][Now^1] 代替了 f[i][j-1] 。为了方便,我们把 f[][Now^1] 叫做 f[][Last] 。

    这样空间复杂度为 O(n) 。满足空间限制。

  2) 对于时间的优化。

    考虑优化状态转移的过程。

    对于 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 的值。

    这样时间复杂度为 O(nm) 。满足时间限制。

 

代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int MaxN = 50000 + 5, Mod = 10007;

int n, m, Len, MaxLen, Ans, Sumf;
int A[MaxN], Sum[MaxN], f[MaxN][2];

inline int gmax(int a, int b) {
	return a > b ? a : b;
}
inline int gmin(int a, int b) {
	return a < b ? a : b;
}

bool Check(int x) {
	if (x < MaxLen) return false;
	int Add = 0, Cut = 0;
	for (int i = 1; i <= n; i++) {
		if (Add + A[i] > x) {
			Cut++;
			if (Cut > m) return false;
			Add = 0;
		}
		Add += A[i];
	}
	return true;
}

int main() 
{
	scanf("%d%d", &n, &m);
	MaxLen = 0;
	memset(Sum, 0, sizeof(Sum));
	for (int i = 1; i <= n; i++) {
		scanf("%d", &A[i]);
		MaxLen = gmax(MaxLen, A[i]);
		Sum[i] = Sum[i - 1] + A[i];
	}
	int l = 0, r = 50000000, mid;
	while (l <= r) {
		mid = (l + r) >> 1;
		if (Check(mid)) r = mid - 1;
		else l = mid + 1;
	}
	Len = r + 1;
	memset(f, 0, sizeof(f));
	Ans = 0; 
	int Now = 0, Last = 1, Mink;
	for (int i = 0; i <= m; i++) {
		Sumf = 0;
		Mink = 1;
		for (int j = 1; j <= n; j++) {
			if (i == 0) {
				if (Sum[j] <= Len) f[j][Now] = 1;
				else f[j][Now] = 0;
			}
			else {
				while (Mink < j && Sum[j] - Sum[Mink] > Len) {
					Sumf -= f[Mink][Last];
					Sumf = (Sumf + Mod) % Mod;
					++Mink;
				}
				f[j][Now] = Sumf;
			}
			Sumf += f[j][Last];
			Sumf %= Mod;
		}
		Ans += f[n][Now]; 
		Ans %= Mod;
		Now ^= 1;
		Last = Now ^ 1;
	}
	printf("%d %d\n", Len, Ans);
	return 0;
}

  

posted @ 2014-11-25 18:30  JoeFan  阅读(1359)  评论(0编辑  收藏  举报