文章目录
0. 前言
状压 dp
就是采用二进制数保存状态,方便进行位运算操作。例如 八皇后、八数码问题也都是采用了状态压缩的思想来使用一个二进制数唯一对应集合中的一个状态。
关键是要体会采用二进制数来表示状态的思想,要转变传统思维,学习接收并吸收这种思想。
1. 状压dp 模板题
这个问题如果暴力来做的话,首先需要确定走过 0~n-1
这些点的顺序,这个就是 n!
,然后再求长度,是 n
,那么总共就是 n!*n
时间爆炸…
状压 dp
思路:
重点: 状压 dp
思路:
- 状态定义:
f[i][j]
:所有从 0 号点走到j
号点,走过的所有点状态压缩为i
的所有路径的最小值。- 状压方式为:因为只有 20 个点,即将
i
看作一个二进制数,取其前 20 位,每一位 01 代表这个点是否走过
- 状态转移:
- 分类依据:所有路径都是从 0 走到
j
的,中间走过的所有点是i
。可以以当前路径的倒数第二个点进行分类,有:- 倒数第二个点是第 0 号点
- 倒数第二个点是第 1 号点
- 倒数第二个点是第 2 号点
- 倒数第二个点是第 n-1 号点
- 则这样就能够不重不漏的将整个状态划分完毕
- 状态转移方程:
f[i][j] = f[i - (1 << j)][k] + w[k][j]
- 分类依据:所有路径都是从 0 走到
- 状态初始化:
f[1][0]=
0,第 0 个点走向自身的距离为 0。由于取min
,故其它f[i][j]=inf
- 返回答案:
f[(1 << n) - 1][n - 1]
走完n
个点(即0~n-1
二进制数位均为 1),且走到第n-1
个点的最小值即为答案
本题非常非常非常经典,是状压 dp
入门经典题目,不论思路还是代码都是很好想的。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 20, M = 1 << N;
int n;
int w[N][N];
int f[M][N];
int main() {
cin >> n;
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
cin >> w[i][j];
memset(f, 0x3f, sizeof f);
f[1][0] = 0;
for (int i = 0; i < 1 << n; ++i) // 枚举所有路径
for (int j = 0; j < n; ++j) // 枚举所有点
if (i >> j & 1) // 如果路径包含该点
for (int k = 0; k < n; ++k) // 枚举转移的点
if ((i - (1 << j)) >> k & 1)// 除去j点,一定包含k这个点,才能走通
f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);
cout << f[(1 << n) - 1][n - 1] << endl; // 走完n个点,且走到第n-1个点的最小值即为答案
return 0;
}