[luoguP2157] [SDOI2009]学校食堂Dining(状压DP)
这种鬼畜的状压DP。。。第一次见
看到 0 <= Bi <= 7 就应该想到状态压缩,然而此题实在太鬼畜,想到也没什么乱用
f[i][j][k]表示前i-1个人全部吃完,i~i+7的人的吃饭状态为j,最后一个吃饭的人和i距离为k
因为有可能第i个人及以后的人都好没有吃,最后一个吃饭的人是前i-1个人中的,考虑Bi的范围,那么 -8 <= k <= 7
所以最后一维统一加上8,防止出现负数下标
如果 j&1,那么f[i][j][k]可以直接转移到f[i+1][j>>1][k-1],那么f[i][j][k]也就没用了,因为这两个状态实质上是一个
否则,枚举集合中新的一个元素来更新f[i][j ^ (1<<l)][l]
初始化:f[i][j][k] = INF,f[1][0][-1] = 0
最终答案:f[n+1][0][-8,0]
#include <cstdio> #include <cstring> #define N 1011 #define f(i, j, k) (g[i][j][k + 8]) #define min(x, y) ((x) < (y) ? (x) : (y)) int T, n, ans; int a[N], b[N], g[N][1 << 8][16]; //g[i][j][k],i表示前i-1个人都吃完了,j表示i~i+7是否吃的集合,k表示最后一个人是i+k吃的 //k属于[-8,7]所以k这一维要统一加上8 inline int calc(int i, int j) { if(!i) return 0; return a[i] ^ a[j]; } int main() { int i, j, k, l, r; scanf("%d", &T); while(T--) { scanf("%d", &n); for(i = 1; i <= n; i++) scanf("%d %d", &a[i], &b[i]); memset(g, 127, sizeof(g)); f(1, 0, -1) = 0; for(i = 1; i <= n; i++) for(j = 0; j < (1 << 8); j++) for(k = -8; k <= 7; k++) if(f(i, j, k) <= 1e9) { if(j & 1) f(i + 1, j >> 1, k - 1) = min(f(i + 1, j >> 1, k - 1), f(i, j, k)); //可以转移的话直接转移到i+1,else的处理到i+1再处理 else { r = 1e9; for(l = 0; l <= 7; l++) if(!(j & (1 << l))) { if(i + l > r) break; r = min(r, i + l + b[i + l]); f(i, j ^ (1 << l), l) = min(f(i, j ^ (1 << l), l), f(i, j, k) + calc(i + k, i + l)); } } } ans = 1e9; //f[n + 1][0][-8,0] for(i = -8; i <= -1; i++) ans = min(ans, f(n + 1, 0, i)); printf("%d\n", ans); } return 0; }