旅行商问题
给定一个 N N N 个顶点组成的带权图 G ( V , E ) G(V, E) G(V,E),要求从顶点 1 出发,经过每个顶点恰好一次后再返回到顶点1,问所经过的边的总权值最小值是多少。
旅行商问题(TSP,Traveling Salesman Problem)是一个著名的 N P NP NP难 问题,没有已知的多项式时间的算法,不过我们还是可以通过设计算法来解决规模较小的 T S P TSP TSP 问题。
容易想到朴素做法的时间复杂度为 O ( n ! ) O(n!) O(n!) 级别,即遍历路径的所有可能情况。状态压缩动态规划可以在指数级时间内求解TSP问题,这也是一个入门状压DP的经典问题。
状压DP解决TSP问题
假设现在已经访问过的顶点集合为
S
S
S ,当前所在的顶点为
v
v
v,用
D
P
[
S
]
[
v
]
DP[S][v]
DP[S][v] 表示从
v
v
v 出发访问剩余所有顶点,最终回到顶点1的路径的权重的最小值。从
v
v
v 出发可以转移到任何一个不在
S
S
S 中的顶点u,因此有如下转移方程:
d
p
[
V
]
[
0
]
=
0
d
p
[
S
]
[
v
]
=
m
i
n
{
d
p
[
S
∪
u
]
[
u
]
+
d
(
v
,
u
)
∣
u
∉
S
}
dp[V][0] = 0 \\ dp[S][v]=min\{dp[S\ \cup \ u][u]+d(v, u)\ |\ u \notin S\}
dp[V][0]=0dp[S][v]=min{dp[S ∪ u][u]+d(v,u) ∣ u∈/S}
这个递推式的下标有一个不是整数,而是一个集合,但是我们可以把它编码成一个整数,把每一个元素是否在集合内对应到一个二进制位里,从而把状态压缩成一个整数,方便计算和维护。
int dp[1<<MAXN][MAXN]
void DP()
{
for(int S=0;S<(1<<n);S++)
{
fill(dp[S], dp[S]+n, INF);
}
dp[(1<<n)-1][1] = 0;
for(int S=(1<<n)-1;S>=0;S--) //枚举状态
{
for(int v=1;v<=n;v++)
{
for(int u=1;u<=n;u++)
{
if(!((S>>u)&1))
dp[S][v] = min(dp[S][v], dp[S|(1<<u)][u]+d[v][u]);
}
}
}
return dp[0][1];
}
这样就可以在 O ( 2 n × n 2 ) O(2^n \times n^2) O(2n×n2) 的时间内完成计算。虽然还是很高,但比起阶乘还是好很多。这样针对集合的DP一般叫状态压缩DP。