状态压缩DP的原理和细节这里不做过多的补充和介绍,重点是给大家展示完整的AC代码,通过精妙、精简的源代码实现理解的深入。
(如果不清楚什么是状压dp,可以参考其他大佬的博客哦( *^-^)ρ(*╯^╰))
简单说明与引入:
状压dp的本质其实也是基于一种暴力枚举算法,不过是借助了bit masking 位运算和二进制0-1特点来记录存储运算,提高算法效率。
注意点:
通常有俩种模型:
1、集合类(加权图等):
e.g.TSP、Hamilton 距离、CPP(中国邮路问题)......
2、连通类\棋盘类:
e.g.Corn Fields、炮兵布阵、Mondriaan's Dream、棋盘填充、小国王......
TIP:
1、预处理dp,保存所有合法转移方案;
2、初始化,建立状态转移方程开始转移;
使用条件:
1、本质是对n!进行暴力遍历,则要求n不是很大的情况下;
2、预处理和初始化要正确;
3、找到关键且分析出正确的状态转移方程;
4、bit masking运算技巧、二进制位运算规则要熟练掌握;
5、有些要有局部最优子结构;
6、状态数k\cnt的引用、状态集state数组的使用都能大大提高优化执行效率;
(接下来直接补充几道状压dp例题的代码,都是自己写了并且完善的精简代码,问题来源都可以通过搜索引擎找到,原理分析同样,这里这是记录ac的精简精妙的代码,还是通过最纯粹的读源码来提升自己的理解哦{{{(>_<)}}})
典型例题:
1.Travelling Salesman Problem
/* 经典NP-C难题TSP 在规模较小的情况下用状压DP求解 状态转移方程 预处理 位运算 局部最优子结构 有向图 */
//本质上也是一种暴力遍历,即枚举算法,利用bit masking状态压缩技巧降低了n!的复杂度
//状压动态规划自底向上
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int INFTY = 0x3f3f3f3f;
const int N = 15 + 5;
const int M = 1 << N;
int dp[N][M];
int graph[N][N];
int main() {
int v, e;
cin >> v >> e;
memset(graph, INFTY, sizeof(graph));
for (int i = 0; i < e; i++) {
int s, t, d;
cin >> s >> t >> d;
graph[s][t] = d;
}
memset(dp, INFTY, sizeof(dp));
int ans = INFTY;
for (int t = 0; t < v; t++) {
memset(dp, INFTY, sizeof(dp));
dp[(1 << v) - 1][t] = 0;
for (int s = (1 << v) - 2; s >= 0; s--) {
for (int i = 0; i < v; i++) {
for (int j = 0; j < v; j++) {
if (!((s >> j) & 1)) {
dp[s][i] = min(dp[s][i], dp[s | (1 << j)][j] + graph[i][j]);
}
}
}
}
ans = min(ans, dp[0][t]);
}
cout << (ans == INFTY ? -1 : ans) << endl;
return 0;
}
2.Chinese Postman Problem
/* CCP中国邮路问题 连通图类状态压缩DP 问题转化 欧拉回路的构建与重复(平行)边的DP Floyd算法点对最短路径 递归 */
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 0xffffff;
const int N = 15 + 5;
int deg[N];
int dp[1 << N];
int graph[N][N];
int n, m;
void floyd() {
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
graph[i][j] = min(graph[i][j], graph[i][k] + graph[k][j]);
}
}
}
}
int search(int state) {
if (state == 0)return 0;
if (dp[state])return dp[state];
int ans = maxn;
int temp;
for (int i = 0; i < n - 1; i++) {
if (state & (1 << i)) {
for (int j = i + 1; j < n; j++) {
if (state & (1 << j)) {
temp = search(state - (1 << i) - (1 << j)) + graph[i][j];
ans = min(ans, temp);
}
}
}
}
return dp[state] = ans;
}
int main() {
while (cin >> n >> m && n) {
int sum = 0;
int state = 0;
int x, y, w;
memset(dp, 0, sizeof(dp));
memset(deg, 0, sizeof(deg));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
graph[i][j] = maxn;
}
}
while (m--) {
cin >> x >> y >> w;
graph[x][y] = graph[y][x] = min(graph[x][y], w);
deg[x]++;
deg[y]++;
sum += w;
}
floyd();
for (int i = 0; i < n; i++) {
if (deg[i] & 1) {
state |= (1 << i);
}
}
sum += search(state);
cout << sum << endl;
}
return 0;
}
3.Corn Fields
/* 经典棋盘类状压DP 二进制位运算bit masking技巧 0 1表示选择技巧 状态数变量优化效率 状态转移方程 预处理的重要性 */
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 12 + 5;
const int M = 1 << N;
const int mod = 1000000000;
int dp[N][M];
int graph[M];
int state[M];
bool judge1(int x) {
return (bool)(x&(x << 1));
}
bool judge2(int x, int y) {
return (bool)(graph[x] & state[y]);
}
int main() {
int n, m;
while (cin >> n >> m && n) {
memset(dp, 0, sizeof(dp));
memset(state, 0, sizeof(state));
memset(graph, 0, sizeof(graph));
int x = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> x;
//状态压缩二进制标记的0 1利用技巧
if (x == 0) {
graph[i] += (1 << j);
}
}
}
int k = 0;
for (int i = 0; i < (1 << m); i++) {
if (!judge1(i)) {
state[k++] = i;
}
}
//预处理
for (int i = 0; i < k; i++) {
if (!judge2(0, i)) {
dp[0][i] = 1;
}
}
for (int i = 1; i < n; i++) {
for (int j = 0; j < k; j++) {
if (judge2(i, j))continue;
for (int f = 0; f < k; f++) {
if (judge2(i - 1, f))continue;
if (!(state[j] & state[f])) {
dp[i][j] += dp[i - 1][f];
dp[i][j] %= mod;
}
}
}
}
int ans = 0;
for (int i = 0; i < k; i++) {
ans += dp[n - 1][i];
ans %= mod;
}
cout << ans << endl;
}
return 0;
}
4.Mondriaan's Dream
/* 状态压缩DP 二进制位运算bit masking技巧 状态转移方程 预处理 暴力枚举优化法 */
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 12;
const int M = 1 << N;
long long f[N][M];
bool state[M];
int main() {
int n, m;
while (scanf("%d%d", &n, &m) == 2 && n) {
for (int i = 0; i < 1 << n; i++) {
state[i] = 1;
int cnt = 0;
for (int j = 0; j < n; j++) {
if ((i >> j) & 1) {
if (cnt & 1)state[i] = 0;
cnt = 0;
}
else {
cnt++;
}
}
if (cnt & 1)state[i] = false;
}
memset(f, 0, sizeof(f));
f[0][0] = 1;
for (int i = 1; i <= m; i++) {
for (int j = 0; j < 1 << n; j++) {
for (int k = 0; k < 1 << n; k++) {
if (((j&k) == 0) && (state[j | k])) {
f[i][j] += f[i - 1][k];
}
}
}
}
cout << f[m][0] << endl;
}
return 0;
}
5.炮兵布阵
/* 炮兵布阵经典棋盘类状压DP 复杂类模拟 二进制位运算bit masking技巧 预处理 三元组表示双层影响 状态转移 */
//0 1 逆表示“棋盘”状态 技巧性很强 便于后面进行位运算(&)进行判断
//非传统方案数类问题 而是增加了一个shell 求最大解 增加dp表示和判断 状态数k/num的引入优化效率
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100 + 5;
const int maxn = 1 << 11;
int dp[N][maxn][maxn];
int state[maxn], num[maxn];
int graph[N];
int n, m, p;
bool check(int x) {
if (x&(x << 1))return false;
if (x&(x << 2))return false;
return true;
}
int count(int x) {
int temp = 0;
int i = 1;
while (i <= x) {
if (i&x)temp++;
i <<= 1;
}
return temp;
}
//预处理出合法状态
void init() {
p = 0;
memset(state, 0, sizeof(state));
memset(num, 0, sizeof(num));
for (int i = 0; i < 1 << m; i++) {
if (check(i)) {
state[p++] = i;
num[p++] = count(i);
}
}
}
int main() {
while (cin >> n >> m && n) {
char ch;
memset(dp, 0, sizeof(dp));
memset(graph, 0, sizeof(graph));
//初始化“棋盘”
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> ch;
if (j) {
graph[i] <<= 1;
}
if (ch == 'H') {
graph[i] |= 1;
}
}
}
init();
//预处理前两行
for (int i = 0; i < p; i++) {
if (!(graph[0] & state[i])) {
dp[0][i][0] = num[i];
}
}
for (int i = 0; i < p; i++) {
if (!(graph[1] & state[i])) {
for (int j = 0; j < p; j++) {
if (!state[i] & state[j]) {
dp[1][i][j] = max(dp[1][i][j], dp[0][j][0] + num[i]);
}
}
}
}
//全局模拟
for (int i = 2; i < n; i++) {
for (int k = 0; k < p; k++) {
if (!(graph[i] & state[k])) {
for (int j = 0; j < p; j++) {
if (!(graph[i - 1] & state[j])) {
if (!(state[k] & state[j])) {
for (int f = 0; f < p; f++) {
if (!(graph[i - 2] & state[f])) {
if (!(state[f] & state[j])) {
if (!(state[f] & state[i])) {
dp[i][k][j] = max(dp[i][k][j], dp[i - 1][j][f] + num[k]);
}
}
}
}
}
}
}
}
}
}
int ans = 0;
for (int i = 0; i < p; i++) {
for (int j = 0; j < p; j++) {
ans = max(ans, dp[n - 1][i][j]);
}
}
cout << ans << endl;
}
return 0;
}
(伏笔就这么多了哦(っ °Д °;)っ)