hihocoder 1044 状态压缩·一 (状态dp)

题意:n堆垃圾,任意连续的m堆中最多选q堆,求最大垃圾量。

思路:dp[i][S]表示前 i 个人中,第i+1-m个人到第i个人的选取情况为S时,能获得的最大值。

先说第一个比较简单的转移方程:dp[i][S],若S里1的个数大于q,则为0。否则在第i-m堆垃圾的取和不取中选较大值,再加上第i堆的取舍情况。

        int cnt = cal(j);//计算出j里面1的个数
	if (cnt <= q){
		dp[i][j] = max(dp[i - 1][j >> 1], dp[i - 1][(j >> 1) | (1 << (m - 1))]) + (j & 1)*w[i];
	}

初始条件dp[1][1]=w[1]。答案就是在dp[n]中取最大

代码如下:

#pragma warning(disable:4996)
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

int w[1005], dp[1005][1 << 10];

int cal(int x){
	int ret = 0;
	while (x){
		if (x & 1)ret++;
		x >>= 1;
	}
	return ret;
}

int main(){

	freopen("in.txt", "r", stdin);

	int n, m, q;
	scanf("%d %d %d", &n, &m, &q);
	for (int i = 1; i <= n; i++)scanf("%d", w + i);

	memset(dp, 0, sizeof dp);
	dp[1][1] = w[1];
	for (int i = 2; i <= n; i++){
		for (int j = 0; j < (1 << m); j++){
			int cnt = cal(j);//计算出j里面1的个数
			if (cnt <= q){
				dp[i][j] = max(dp[i - 1][j >> 1], dp[i - 1][(j >> 1) | (1 << (m - 1))]) + (j & 1)*w[i];
			}
		}
	}

	int ans = 0;
	for (int j = 1; j < (1 << m); j++){
		ans = max(ans, dp[n][j]);
	}
	printf("%d\n", ans);
	return 0;
}

另外一种容易出错的转移方程:

                        //这里就是容易出错的地方
			/*int s1 = (j << 1) | 1;
			int s0 = (j << 1);*/
			int s0 = ((j << 1) & ((1 << m) - 1));
			int s1 = ((j << 1 | 1) & ((1 << m) - 1));
			if (s0 >= (1 << m))continue;
			if (cal(s1) <= q){
				dp[i][s1] = max(dp[i][s1], dp[i - 1][j] + w[i]);
			}
			dp[i][s0] = max(dp[i][s0], dp[i - 1][j]);

解释一下为什么注释起来的地方是不对的。

因为我们是用集合S来表示m个元素的取舍情况,但是j<<1的话可能是S的元素个数大于m(这时候就容易漏掉状态),所以我们要按位与上m个1,即(1<<m)-1。

你可能感兴趣的:(hihocoder 1044 状态压缩·一 (状态dp))