记得以前做N皇后问题见到过二进制+位运算优化的方法, 今天的搜索题第三题和第四题都可以用到二进制和位运算. 就只做了这两个题目.
题目三
描述
传递游戏(pass)
Description
n个人在做传递物品的游戏,编号为1-n。
游戏规则是这样的:开始时物品可以在任意一人手上,他可把物品传递给其他人中的任意一位;下一个人可以传递给未接过物品的任意一人。
即物品只能经过同一个人一次,而且每次传递过程都有一个代价;不同的人传给不同的人的代价值之间没有联系;
求当物品经过所有n个人后,整个过程的最小总代价是多少。
Input Format
第一行为n,表示共有n个人(16>=n>=2);
以下为n*n的矩阵,第i+1行、第j列表示物品从编号为i的人传递到编号为j的人所花费的代价,特别的有第i+1行、第i列为-1(因为物品不能自己传给自己),其他数据均为正整数(<=10000)。
Output Format
一个数,为最小的代价总和。
分析
- 状压DP, 二进制表示状态(点到过或没到过), 位运算实现查找和更新等操作
f[i][k]
表示状态为 k 并且最后停留在 i 点时的最小代价- =>
min{f[i][(1<<n)+1], (0<=i<n)}
- 转移 :
枚举状态 k : 0 -> (1 << n) - 1
枚举要更新的结点 i : 0 -> n-1
枚举中间结点 j : 0 -> n-1, (i != j)
如果满足 在状态 k 中第 i 的位置为 0, 而第 j 的位置为 1, 那么就可以用 j 去更新 i.
- 核心 :
if(i != j && (k&(1 << j)) && !(k&(1 << i))) f[i][k^(1 << i)] = min(f[i][k^(1 << i)], f[j][k] + d[j][i]);
- 初始化
f[i][1 << i] = 0, (0<=i<n)
表示从任意点出发
代码
题目四
描述
皇后守卫(queen)
Description
给一个N * M的棋盘,棋盘上的有些格子被打上了标记。现在需要在其中放置尽量少的皇后,使得所有被打上标记的格子至少被某一个皇后攻击或占据到。皇后之间可以互相攻击。
Input Format
输入最多15组数据。
每组数据第一行包含两个整数N和M(1 < N, M < 10),以下为一个N行M列的棋盘,其中打上标记的格子用‘X’表示,其它格子用‘.’表示。
输入以一个0结尾。
Output Format
对于每组数据,输出一个数表示最少需要使用的皇后数目。
分析
- 二进制+位运算优化+普通最优化剪枝的 dfs
void dfs(int x, int row, int ld, int rd, int* k, int c)
x => 当前行
row => 这一行被列控制下的上禁止放的位置
ld => 这一行被左对角线控制下的不能放的位置
rd => 这一行被右对角线控制下的不能放的位置
k => 数组, 表示当前所有被覆盖的位置, 每一行都是用二进制表示的, 所以数组只需开一维
c => 计数变量, 已经放的皇后数
主函数调用
dfs(0, 0, 0, 0, k, 0)
// k 初始化全为 0- 函数执行过程
如果该状态下满足了需要, 直接更新答案后返回
如果所以行已经考虑完了(x >= n), 直接返回
最优化剪枝 : 如果当前 c 不比已经记录的 ans 更优, 直接返回
从当前行中选可以放置皇后的地方开始放皇后, 继续dfs到下一层
该行不放皇后, dfs到下一层
- 许多精巧的位运算 :
- 在 row, ld, rd 限制下取出所有可以放置皇后的地方 =>
pos = ((1<<n)-1) & ~(row | ld | rd)
解释 :
row | ld | rd
表示所有不可以放置皇后的地方, 取反后表示所有可以放置皇后的地方, 再并一下去除前面多余的1- 判断是否满足条件, 标记的地方都被覆盖到
枚举 x, 如果所有
(k[x]&tar[x]) == tar[x]
说明满足解释 :
tar[x]
表示第 x 行需要被覆盖的二进制状态. 只有当 tar[x] 为 1 的地方 k[x] 也为 1 才满足- 更新状态, 在第 x 行二进制下 p 位置放置皇后对原可覆盖状态 k 的影响
void update(int x, int p, int* k) { k[x] = upperlim; for(int i = 1; x-i >= 0; i++) { if((p<<i) < upperlim) k[x-i] |= (p<<i); if((p>>i) > 0) k[x-i] |= (p>>i); k[x-i] |= p; } for(int i = 1; x+i < n; i++) { if((p<<i) < upperlim) k[x+i] |= (p<<i); if((p>>i) > 0) k[x+i] |= (p>>i); k[x+i] |= p; } }
解释 :
upperlim = (1 << n) - 1
首先 x 行应该全变为 1, 也就是 k[x] 状态为 upperlim
再考虑对角线和列
对p的解释 : p 的二进制中只有一位为 1, 也就是皇后所在列的那一位
代码
https://code.csdn.net/snippets/609744