状压dp类型:ios
连通性dp的状态压缩表示的是每一个点的位置关系,集合类dp的状态压缩表示的是每一个点的是否存在c++
状压dp特色:
处理的棋盘的规模很小,通常n、m规模都在60之内算法
acwing291蒙德里安的梦想
题意: 求把N * M的棋盘分割成若干个1 * 2的的长方形,有多少种方案。
例如当N=2,M=4时,共有5种方案。当N=2,M=3时,共有3种方案。
以下图所示:
1≤N,M≤11
题解: 仔细考虑就能够知道所有的方案是取决于横的方块的方案,一旦横的方块肯定后竖的方块也就肯定了。使用f[i][j]表示前i-1列填完,第i列为j的状况,那么可以合法填充的j和k知足,j&k==0且j|k之间连续的1为偶数。那么考虑dp的转移方程为:f[i][j]+= f[i-1][k] (k是和j可以匹配的合法方案)
代码:数组
#include<bits/stdc++.h> using namespace std; typedef long long LL; int n, m; int const N = 1e4 + 10; LL f[20][N]; int st[N]; vector<int> state[N]; int main() { while (cin >> n >> m && n && m) { // 初始化 memset(f, 0, sizeof f); f[0][0] = 1; for (int i = 0; i < 1 << n; ++i) state[i].clear(); // 预处理st for (int i = 0; i < 1 << n; i ++ ) { int cnt = 0; st[i] = true; for (int j = 0; j < n; j ++ ) if (i >> j & 1) { if (cnt & 1) st[i] = false; cnt = 0; } else cnt ++ ; if (cnt & 1) st[i] = false; } // 预处理state for (int i = 0; i < 1 << n; ++i) { for (int j = 0; j < 1 << n; ++j) { if ((i & j) == 0 && st[i | j]) state[i].push_back(j); } } // dp转移 for (int i = 1; i <= m; ++i) { for (int j = 0; j < 1 << n; ++j) { for (int k = 0; k < state[j].size(); ++k) // 得到全部的合法方案 { f[i][j] += f[i - 1][state[j][k]]; } } } printf("%lld\n", f[m][0]); } return 0; }
acwing1064骑士
题意: 在 n×n 的棋盘上放 k 个国王,国王可攻击相邻的 8 个格子,求使它们没法互相攻击的方案总数。1≤n≤10,0≤k≤n^2^
题解: 状压dp模板题
代码:优化
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N = 12, M = 1 << 10, K = 110; int n, m; vector<int> state; int cnt[M]; vector<int> head[M]; LL f[N][K][M]; // 计算每种状况是否合法 bool check(int state) { for (int i = 0; i < n; i ++ ) if ((state >> i & 1) && (state >> i + 1 & 1)) return false; return true; } // 计算每种状况内的1 int count(int state) { int res = 0; for (int i = 0; i < n; i ++ ) res += state >> i & 1; return res; } int main() { cin >> n >> m; // 记录全部合法的状况,同时计算出每种合法状况的1数目 for (int i = 0; i < 1 << n; i ++ ) if (check(i)) { state.push_back(i); cnt[i] = count(i); } // 计算哪两种合法状况间可以互相匹配 for (int i = 0; i < state.size(); i ++ ) for (int j = 0; j < state.size(); j ++ ) { int a = state[i], b = state[j]; if ((a & b) == 0 && check(a | b)) head[i].push_back(j); } // 状态转移:f[i][j][a]:到第i行,使用了j个,第i行填充a f[0][0][0] = 1; // 本题入口与蒙德里安的梦想不一样是由于本题的第一列能够填东西,而蒙德里安的梦想第一列不能够填东西 for (int i = 1; i <= n + 1; i ++ ) for (int j = 0; j <= m; j ++ ) for (int a = 0; a < state.size(); a ++ ) for (int b : head[a]) // 第i-1行填充b { int c = cnt[state[a]]; if (j >= c) f[i][j][a] += f[i - 1][j - c][b]; } cout << f[n + 1][m][0] << endl; // 输出第n+1行,使用了m个,第n+1行填充0的状况 return 0; }
acwing327玉米田
题意: 农夫约翰的土地由M*N个小方格组成,如今他要在土地里种植玉米。很是遗憾,部分土地是不育的,没法种植。并且,相邻的土地不能同时种植玉米,也就是说种植玉米的全部方格之间都不会有公共边缘。如今给定土地的大小,请你求出共有多少种种植方法。土地上什么都不种也算一种方法。1≤M,N≤12
题解: 状压dp模板题
代码:spa
#include<bits/stdc++.h> using namespace std; typedef long long LL; int n, m; int const N = 14, M = 1 << N, MOD = 1e8; LL f[N][M]; int g[N]; vector<int> st; vector<int> state[M]; bool check(int x) { for (int i = 0; i < m - 1; ++i) if ((x >> i & 1) && (x >> (i + 1) & 1)) return false; return true; } int main() { // 初始化玉米田地 cin >> n >> m; for (int i = 1 ; i <= n; ++i) { for (int j = 0; j < m; ++j) { int t; cin >> t; g[i] += (!t << j); } } // 找到全部合法方案 for (int i = 0; i < 1 << m; ++i) { if (check(i)) st.push_back(i); } // 找到能够互相匹配的合法方案 for (int i = 0; i < st.size(); ++i) for (int j = 0; j < st.size(); ++j) { int a = st[i], b = st[j]; if ((a & b) == 0) state[i].push_back(j); } // 初始化 memset(f, 0, sizeof f); f[0][0] = 1; // 状态转移f[i][j]:第i行使用第j种合法方案 for (int i = 1; i <= n + 1; i ++ ) // 第i行 for (int j = 0; j < st.size(); j ++ ) // 第j种合法方案 if (!(st[j] & g[i])) // 若是第j种合法方案和当前玉米地不匹配 for (int k : state[j]) // 第i-1行选择合法方案k f[i][j] = (f[i][j] + f[i - 1][k]) % MOD; cout << f[n + 1][0] << endl; // 第n+1行选择合法方案0的状况(即000000) return 0; }
acwing292 炮兵阵地
题意: 司令部的将军们打算在N * M的网格地图上部署他们的炮兵部队。一个N * M的地图由N行M列组成,地图的每一格多是山地(用”H” 表示),也多是平原(用”P”表示),以下图。在每一格平原地形上最多能够布置一支炮兵部队(山地上不可以部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
若是在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它可以攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。如今,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其余支炮兵部队的攻击范围内),在整个地图区域内最多可以摆放多少我军的炮兵部队。输出最多能摆放的炮兵部队的数量。N≤100,M≤10
题解:code
代码:ci
#include <cstring> #include <iostream> #include <algorithm> #include <vector> using namespace std; const int N = 10, M = 1 << 10; int n, m; int g[1010]; int f[2][M][M]; vector<int> state; int cnt[M]; bool check(int state) { for (int i = 0; i < m; i ++ ) if ((state >> i & 1) && ((state >> i + 1 & 1) || (state >> i + 2 & 1))) return false; return true; } int count(int state) { int res = 0; for (int i = 0; i < m; i ++ ) if (state >> i & 1) res ++ ; return res; } int main() { cin >> n >> m; for (int i = 1; i <= n; i ++ ) for (int j = 0; j < m; j ++ ) { char c; cin >> c; g[i] += (c == 'H') << j; } for (int i = 0; i < 1 << m; i ++ ) if (check(i)) { state.push_back(i); cnt[i] = count(i); } for (int i = 1; i <= n; i ++ ) for (int j = 0; j < state.size(); j ++ ) for (int k = 0; k < state.size(); k ++ ) for (int u = 0; u < state.size(); u ++ ) { int a = state[j], b = state[k], c = state[u]; if (a & b | a & c | b & c) continue; if (g[i] & b | g[i - 1] & a) continue; f[i & 1][j][k] = max(f[i & 1][j][k], f[i - 1 & 1][u][j] + cnt[b]); } int res = 0; for (int i = 0; i < state.size(); i ++ ) for (int j = 0; j < state.size(); j ++ ) res = max(res, f[n & 1][i][j]); cout << res << endl; return 0; }
acwing 91. 最短Hamilton路径
题意: 给定一张 n 个点的带权无向图,点从 0 ~ n-1 标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地通过每一个点刚好一次。输出一个整数,表示最短Hamilton路径的长度。1≤n≤20,0≤a[i,j]≤10^7^
题解: 本题若是纯暴力那么共有n!种状况
所以能够考虑使用二进制来优化时间复杂度。第一维枚举每一个点是否被使用过,第二维和第三维分别枚举当前是在哪一个点上,经过不断的三角不等式更新,最后就能够取得最小值。这个题本质就是对floyd的一种优化
代码:部署
#include<bits/stdc++.h> using namespace std; int const N = 1 << 20; int f[N][21]; int n; int g[21][21]; int main() { // 读入边 cin >> n; for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) cin >> g[i][j]; // 初始化 memset(f, 0x3f, sizeof f); f[1][0] = 0; // 更新f数组 for (int i = 0; i < 1 << n; ++i) // 枚举每一种状况 { for (int j = 0; j < n; ++j) // 枚举第1个点 { if (i >> j & 1) { for (int k = 0; k < n; ++k) // 枚举第2个点 { if ((i - (1 << j)) >> k & 1) { f[i][j] = min(f[i][j], f[i - (1 << j)][k] + g[k][j]); // 三角不等式更新 } } } } } cout << f[(1 << n) - 1][n - 1] << endl; return 0; }