状态压缩通常就是对二进制而言的,在学习树状数组的时候,一个特别重要的东西 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;
}