状态压缩动态规划(接上文伏笔)

状态压缩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;
}

(伏笔就这么多了哦(っ °Д °;)っ)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敲码的钢珠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值