状态压缩通常就是对二进制而言的,在学习树状数组的时候,一个特别重要的东西 x & (-x) , 表示的含义是去掉 x 高位的 1 , 只保留最低位的 1 , 如果 x 在二进制下只有一个 1, 那这样得到的数就是它本身。
x ^ (x&-x) 表示的含义是去掉最低位的 1 个 1, 这个式子等价于 x - (x&-x)
来看一道装压的入门题 :
给你一个n*n的格子的棋盘,每个格子里面有一个非负数。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大。Input包括多个测试实例,每个测试实例包括一个整数n 和n*n个非负数(n<=20)Output对于每个测试实例,输出可能取得的最大的和Sample Input
3 75 15 21 75 15 28 34 70 5
Sample Output
188
题目分析 : 这题可以用装压水过去,首先我们要处理的是每行可以选定的状态有哪些,然后预处理出来这些状态下对应的花费总和
dp[i][j] 表示的含义是在第 i 行,选取 j 状态下的最大花费
在这里有 2 个判定条件要注意下,就是如何用二进制判断在不在同一行 x&(x>>1), 如何这个条件成立,则说明有两个 1 之间是相邻的,
判断同上一行的位置关系是否满足,只需要判断 x & y 是否等于 1 ,若等于1则说明上下两行有 1 相邻
代码示例 :
#define ll long long const int maxn = 1e5+5; const double pi = acos(-1.0); const int inf = 0x3f3f3f3f; int n; int mp[25][25]; int dp[25][maxn]; // 第 i 行取第 j 个状态时的最大和 int sum[25][maxn]; // 第 i 行取第 j 个状态的花费 int s[maxn]; int p; bool checkh(int x){ return !(x&(x>>1)); } bool checkl(int x, int y){ return !(x&y); } void init() { p = 0; for(int i = 0; i < (1<<n); i++){ if (checkh(i)) s[p++] = i; } //printf("p = %d\n", p); for(int i = 1; i <= n; i++) { for(int j = 0; j < p; j++){ int state = s[j]; int res = 0; int pos = n; while(state){ if (state & 1) res += mp[i][pos]; state >>= 1; pos--; } sum[i][j] = res; } } for(int i = 0; i < p; i++){ dp[1][i] = sum[1][i]; } } int main() { //freopen("in.txt", "r", stdin); //freopen("out.txt", "w", stdout); while(~scanf("%d", &n)){ memset(dp, 0, sizeof(dp)); // 初始化 !! for(int i = 1; i <= n; i++){ for(int j = 1; j <= n; j++){ scanf("%d", &mp[i][j]); } } init(); for(int i = 2; i <= n; i++){ for(int j = 0; j < p; j++){ // 枚举当前行的全部状态 for(int k = 0; k < p; k++) { // 枚举上一行的全部状态 if(checkl(s[j], s[k])) { dp[i][j] = max(dp[i][j], dp[i-1][k]+sum[i][j]); } } } } int ans = dp[n][0]; for(int i = 1; i < p; i++) ans = max(ans, dp[n][i]); printf("%d\n", ans); } return 0; }