状压DP
引入:经典旅行商(TSP)问题。
-
用一个数的二进制表示一个集合:
有结点V = {0, 1, 2, 3, 4, 5}
26(10) = 011010(2)
表示经过结点V1 = {1, 3, 4} -
状态表示:
-
转移方程:
(标号从0开始)
如果标号从1开始,则改为S|(1<<(j-1)),不然会浪费一位二进制位。
G[i][j]是从i到j的权值
初始状态:dp[i][1<<i] = 0;
(没有限制从哪个点出发,且初始权值点为0) -
子集枚举:
可以保证子集在之前都被枚举过
核心代码:
法一:
for (int S = 0; S < (1 << n); ++S)
for (int i = 0; i < n; ++i)
if (S & (1 << i)) //如果i节点在S所表示的集合中
for (int j = 0; j < n; ++j)
if (!(S & (1 << j)) && G[i][j]) //从i到j,原状态中不能有j
dp[j][S | (1 << i)] = min(dp[j][S | (1 << i)], dp[i][S] + G[i][j]);
法二:
for (int S = 0; S < (1 << n); ++S)
for (int i = 0; i < n; ++i)
if (S & (1 << i))
for (int j = 0; j < n; ++j)
if ((S & (1 << j)) && G[j][i]) //从j到i,原状态中要有j
dp[i][S] = min(dp[i][S], dp[j][S ^ (1 << i)] + G[j][i]); //S^(1<<i)是删除集合中的i节点
ex problem:
https://www.acwing.com/problem/content/93/