浅谈状压dp旅行商类问题
本篇文章对新人友好,可以作为状压 DP 和进制压缩思想的入门文章
前置知识:二进制操作
- 本篇文章中,称二进制的最低位为第 \(0\) 位,依次向高编号至第 \(n-1\) 位。这正好对应了二进制的位权。
二进制枚举
通过一串二进制数字表示物品的状态。
如 \(101\) 表示选第一个、第三个物品,那么三个物品的所有状态就是
000
001
010
011
100
101
110
111
正好对应 \(0\sim2^3-1\) 的二进制编码,可以用循环枚举,因此叫二进制枚举。
位运算操作二进制数
常用的二进制操作有以下几种。
取出 \(x\) 的二进制第 \(i\) 位:
(x>>i)&1 或 (1<<i)&x
将 \(x\) 的第 \(i\) 位二进制改为 \(0\):
x=x&~(1<<i)
将 \(x\) 的第 \(i\) 位二进制改为 \(1\):
x=x|(1<<i)
将 \(x\) 的二进制的最后一个 \(1\) 去掉:
x=x&(x-1)
统计二进制中 \(1\) 的个数:
__builtin_popcount(x)
(也可以手写)
状态压缩DP
有些 DP 问题的状态设计形如 \(dp[...][0/1][0/1][...][0/1][0/1]\),其中 \(1\) 表示选中,\(0\) 表示不选。这会导致写起来特别麻烦。
此时,我们可以把这些 \([0/1]\) 维度全部压缩在一个二进制数里。
这种通过将状态集合转化为整数记录在 DP 状态中来实现状态转移目的 DP 称为状态压缩 DP,简称状压 DP。核心思想:将多维度的状态压缩成一个整数,以整数(通常是二进制数)的每一位来表示一个元素的状态。
状压dp中的旅行商问题
下面用例题:P10447 最短 Hamilton 路径来讲解状态压缩 DP 中的旅行商类问题。
题目大意:求无向带权图中的最短一笔画路径。起点为 \(0\),终点为 \(n-1\)。\(1\le n\le 20\)
暴力:全排列枚举,时间复杂度 \(O(n\times n!)\),显然超时。
可以注意到,假设我们目前已经访问过 \(1\),\(2\),\(4\),\(5\) 号点,现在停在 \(4\) 号点,此时理论上有 多少种方案可以到达这里?
\(1\rightarrow 2\rightarrow 5\rightarrow 4\)
\(1\rightarrow 5\rightarrow 2\rightarrow 4\)
\(2\rightarrow 1\rightarrow 5\rightarrow 4\)
\(2\rightarrow 5\rightarrow 1\rightarrow 4\)
\(5\rightarrow 1\rightarrow 2\rightarrow 4\)
\(5\rightarrow 2\rightarrow 1\rightarrow 4\)
有 \(6\) 种方案,但是未来只会用到代价最小的那一种,其他的不用记录。
那么对于这个状态的,有没有可能通过子问题得到?
有的兄弟有的,最后一步为 \(4\), 那么前面肯定都是 \(1\)、\(2\)、\(5\) 已经到过,停在哪不确定 到过 \(1\)、\(2\)、\(5\) 的路径即为子问题,但不确定停在哪。
所以我们找到了子问题以及相关参数,那么就可以开始 DP 了。
设 \(dp[state][j]\) 为当前走过的节点的状态为 \(state\),并且现在停留在点 \(j\) 时的最短路径长度。
其中,\(state\) 为一个二进制数,如果其第 \(i\) 位为 \(1\),代表节点 \(i\) 被访问过,否则没被访问过。
假设我们现在要转移到 \(dp[state][j]\),那么上一个点必然是某个已经在 \(state\) 中的点 \(k\)(且
\(k\neq j\))。
在到达点 \(j\) 之前,\(j\) 没有被访问过,所以要把 \(state\) 的第 \(j\) 位改为 \(0\),这里就用到了前置知识中的二进制操作,此时状态为 \(state \oplus (1 << j)\)。(\(\oplus\) 为异或)
先自己推推转移方程,比较好推,推不出来再展开。
点击展开转移方程
其中 \(w(k,j)\) 表示路径 \((w,k)\) 的长度。
答案在哪里呢?要全部被访问过,那状态就要全是 \(1\),最终停止的点要在 \(n-1\),所以答案为 \(dp[(1<<n)-1][n-1]\)。
初始时只有节点 \(0\) 被访问过,当前也停留在节点 \(0\),所以 \(dp[1][0]=0\)。其余的状态还不能到达,设为极大值。
状压 DP 在实现里会有许多细节,具体请看代码。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int d[25][25],dp[1<<20][21];
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>d[i][j];
memset(dp,0x3f,sizeof dp);
dp[1][0]=0;
for(int i=1;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){
dp[i][j]=min(dp[i][j],dp[i^(1<<j)][k]+d[k][j]);
}
}
}
}
}
cout<<dp[(1<<n)-1][n-1];
return 0;
}
这就是状态压缩 DP 的基本思路,用二进制数表示当前的状态。
像最短 Hamilton 路径这种阶乘枚举问题转化指数枚举问题,每次变化为一个单点的问题,就叫做旅行商类状压 DP 问题,是状压 DP 的经典应用场景。至于为啥叫旅行商,咱也不知道。
变式
题目大意:求经过所有城市至少一次的最小花费,但是!每个城市最多可以经过两次。
由于每个点可以经过两次,我们就不能用简单的二进制来表示访问的状态了,因为有 没经过/经过一次/经过两次 三种状态。怎么办呢?
二进制不行,我就三进制!
依旧设 \(dp[state][j]\) 为当前走过的节点的状态为 \(state\),并且现在停留在点 \(j\) 时的最小花费。
这次,\(state\) 为一个三进制数,如果其第 \(i\) 位为 \(k\),代表节点 \(i\) 被访问过 \(k\) 次,\(0\) 即没被访问过。
但计算机没有内置的三进制位运算,我们只能手搓。
\(x/3^i\%3\) 表示三进制下 \(x\) 的第 \(i\) 位。
\(x+3^i\) 表示将三进制下 \(x\) 的第 \(i\) 位加一。
转移方程也比较简单了,和最短 Hamilton 路径的差不多。
点击展开转移方程
条件是:\(mask\) 中 \(v\) 位的数值必须小于 \(2\),才能走过去,走过去后 \(v\) 位的值加 \(1\)。
代码中,我们预处理出三进制下每一位的位权,定义 \(tri[i]\) 表示第 \(i\) 条路径上城市 \(j\) 的状态,然后就可以愉快的 DP 了!
点击查看代码
#include<bits/stdc++.h>
#define debug cout<<"!";
using namespace std;
const int inf=0x3f3f3f3f;
int n,m;
int pow3[15];
int tri[60000][13],dp[13][60000],d[13][13];
void init(){
pow3[1]=1;
for(int i=2;i<=13;i++) pow3[i]=pow3[i-1]*3;
for(int i=0;i<59050;i++){
int t=i;
for(int j=1;j<=10;j++){
tri[i][j]=t%3;
t/=3;
}
}
}
int solve(){
int ans=inf;
memset(dp,inf,sizeof dp);
for(int i=0;i<=n;i++) dp[i][pow3[i]]=0;
for(int i=1;i<pow3[n+1];i++){
int firefragments=1;
for(int j=1;j<=n;j++){
if(tri[i][j]==0){
firefragments=0;
continue;
}
for(int k=1;k<=n;k++){
int l=i-pow3[j];
// cout<<l<<" ";
dp[j][i]=min(dp[j][i],dp[k][l]+d[k][j]);
}
}
if(firefragments){
for(int j=1;j<=n;j++) ans=min(ans,dp[j][i]);
}
}
return ans;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
init();
while(cin>>n>>m){
memset(d,0x3f,sizeof dp);
while(m--){
int x,y,w;
cin>>x>>y>>w;
if(w<d[x][y]) d[x][y]=d[y][x]=w;
}
int ans=solve();
if(ans==inf) cout<<"-1\n";
else cout<<ans<<"\n";
}
return 0;
}
练习题:P1171 售货员的难题、P10963 [ICPC-Shanghai 2004] Islands and Bridges、POJ-3311 Hie with the Pie。

浙公网安备 33010602011771号