状压DP棋盘放置问题如何解决?
摘要:其实没什么好说的,就是在棋盘上的。 题目 P2915 [USACO08NOV] Mixed Up Cows G(混入的) 这个应该算是旅行商问题。 解法 状态定义: (dp_{i,state}) 表示选择状态为 (state),以第
其实没什么好说的,就是在棋盘上的。
题目
P2915 [USACO08NOV] Mixed Up Cows G(混入的)
这个应该算是旅行商问题。
解法
状态定义:
\(dp_{i,state}\) 表示选择状态为 \(state\),以第 \(i\) 头奶牛为结尾的方案数。
状态转移方程式
\[dp_{j,s|(1<<j)} = \sum dp_{i,s}
\]
哦对了!不要忘记了特殊条件!
相邻奶牛的编号之差均超过K
代码
戳我喵~
int a[17];
lint dp[17][(1 << 16) + 10];
int main() {
int n = re, k = re;
for (int i = 0; i < n; i++) a[i] = re;
for (int i = 0; i < n; i++) dp[i][1 << i] = 1;
for (int s = 0; s < (1 << n); s++) {
for (int i = 0; i < n; i++) {
if ((s >> i) & 1) {
for (int j = 0; j < n; j++) {
if (!((s >> j) & 1) && abs(a[j] - a[i]) > k) {
dp[j][s | (1 << j)] += dp[i][s];
}
}
}
}
}
lint ans = 0;
for (int i = 0; i < n; i++) ans += dp[i][(1 << n) - 1];
wr(ans), endl;
}
P1879 [USACO06NOV] Corn Fields G
这题太经典了!
解法
先看数据范围:\(1 \le n,m \le 12\)
可以状压!
我们注意力惊人,发现了只有与它相邻的行与列才有关,所以我们考虑压缩一行的状态。
所以我们定义 \(dp_{i,j}\) 表示第 \(i\) 行,采用了第 \(j\) 个合法状态时的方案总数。
可是合法状态怎么统计呢?
我们可以在预处理阶段处理这合法状态。
合法状态即满足题目的条件,即不能有两块奶牛所在的草地相邻。
我们只需要判断 i & (i >> 1) 是否为 \(0\),如果为 \(0\),那么它就是中意的方案。
最后的 \(DP\) 还算简单,看代码就能理解。
状态转移方程式:
\[dp_{i,j} = \sum dp_{i - 1,k}
\]
代码
戳我喵
lint f[20], dp[20][(1 << 15) + 10]; //f是用来记录草地状态的
int n, m;
int main() {
IAKIOI;
n = re, m = re;
for (int i = 1; i <= n; i++) for (int j = 0, x; j < m; j++) {
x = re;
if (x) f[i] |= (1 << j);
}
vector<int> v;
for (int i = 0; i < (1 << m); i++) if ((i & (i >> 1)) == 0) v.push_back(i);
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < v.size(); j++) {
if ((v[j] & f[i]) == v[j]) {
for (int k = 0; k < v.size(); k++) {
if (!(v[j] & v[k])) {
dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mod;
}
}
}
}
}
lint ans = 0;
for (int i = 0; i < v.size(); i++) ans = (ans + dp[n][i]) % mod;
wr(ans), endl;
}
再来一个变式
P2704 [NOI2001] 炮兵阵地
这个其实就是可以覆盖的范围更大了嘛!
解法
参考T2
\(dp_{i,j,k}\) 表示第 \(i\) 行状态为 \(v_j\),第 \(i - 1\) 行状态为 \(v_k\) 时的最大数量
把T2改一下就行了,在一行里,需要改条件(s & (s << 1)) == 0 且 (s & (s << 2)) == 0
代码
戳我喵~
int f[110], dp[101][701][701];
int n, m;
int main() {
n = re, m = re;
for (int i = 1; i <= n; i++) for (int j = 0; j < m; j++) {
char ch;
cin >> ch;
if (ch == 'P') f[i] |= (1 << j);
}
vector<int> v, cnt; //cnt表示的是此状态下的炮兵数量
for (int i = 0; i < (1 << m); i++) {
if ((i & (i >> 1)) == 0 && (i & (i >> 2)) == 0) {
v.push_back(i), cnt.push_back(__popcount(i));
}
}
dp[0][0][0] = 0;
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int x = 0; x < v.size(); x++) {
if ((v[x] & f[i]) == v[x]) {
for (int y = 0; y < v.size(); y++) {
if ((v[y] & f[i - 1]) == v[y]) {
for (int z = 0; z < v.size(); z++) {
if ((v[z] & f[i - 2]) == v[z] && !(v[x] & v[y]) && !(v[x] & v[z]) && !(v[z] & v[y])) {
dp[i][x][y] = max(dp[i][x][y], dp[i - 1][y][z] + cnt[x]); //取得是最大值,而不是方案数和
}
}
ans = max(ans, dp[i][x][y]);
}
}
}
}
wr(ans), endl;
}
练习题
P1896 [SCOI2005] 互不侵犯
P10449 费解的开关(可以用双向搜索)
