状态压缩DP
DP过程中的状态不可能像背包问题一样只有整数,肯定有各种各样稀奇古怪的状态,需要不止一个变量来表示。这种情况下如果需要使用DP 就必须把状态压缩成一个数来表示,并且一个数只能对应于一种状态。
特别地,对于集合我们可以把每一个元素的选取与否对应到一个二进制位里,从而把状态压缩成一个整数,大大方便了计算和维护。
对于不是整数的情况,很多时候很难确定一个合适的递推顺序,因此使用记忆化搜索可以避免这个问题。如下面TSP问题的法一。
TSP问题
一张图上有n个点,给定相应的邻接矩阵,需要求出从0号节点出发,经过且只经过每个顶点一次,最后仍回到0号节点的最小边权。TSP问题可以用状压DP来快速求解。
定义dp[S][v]为已经经过了点集S之后,目前在点v(v已经包含在S中),回到0节点的最小边权。
所以有如下的递推公式
**dp[V[0] = 0
dp[S][v] = min(dp[S∪{u}][u]+d[v][u])(其中u不属于S)**
将S看作一个长度为n的bit流,第几号节点访问过就把S的第几号节点置为1,其他都是0,这样就可以将状态压缩成了一个数字来表示,并且有一一对应性。
采用记忆化搜索的TSP状压DP代码如下
int n;
int d[maxn][maxn];
int dp[1<<maxn][maxn];
int rec(int S,int v)
{
if(dp[S][v] >= 0) {
return dp[S][v];
}
if(v == 0 && S == (1<<maxn)-1) return dp[S][v] = 0;
int ans = INF;
for(int i = 0 ; i < n ; i ++) {
if(!(S>>i&1)) {
ans = min(ans,d[v][i]+rec(S|(1<<i),i));
}
}
return dp[S][v] = ans;
}
void solve(void)
{
memset(dp,-1,sizoef(dp));
cout << rec(0,0) << endl;
}
此外也可以不用记忆化搜索,观察递推的顺序从而使用循环求解。
发现对于任意的两个整数i和j 如果它们对应的集合满足S(i)包含于S(j) 那么i<=j。代码如下
int dp[1<<maxn][maxn];
void solve()
{
for(int i = 0 ; i < (1<<n) ; i ++) {
fill(dp[S],dp[S]+n,INF);
}
dp[(1<<n)-1][0] = 0;
for(int S = (1<<n)-1 ; S >= 0 ; S --) {
for(int i = 0 ; i < n ; i ++) {
for(int j = 0 ; j < n ; j ++) {
if(!(S>>j&1))
dp[S][i] = min(dp[S][i],dp[S|1<<j][j]+d[i][j]);
}
}
}
cout << dp[0][0] << endl;
}
时间: 2024-10-23 00:51:44