题意: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。