当n比较小的时候(<=20)有可能可以通过装压DP求解最优值或方案数,在状态设计时有一维由压缩的数表示,方便对各种情况的处理以及减少编程难度&时间复杂度。
其实为什么要装压呢?比如我们完全可以设计f[a][b][c][d][e][f][g][h][......]描述状态,但是这样是不是看起来很丑呢?我们完全可以设f[i]表示状态为i时的情况,是不是美观多了?
TSP问题是装压DP的一种,状态常常设计成f[i][j]表示状态为i,当前在j时的最小花费。
有时候我们要先做一遍floyd求出两点之间最小路径长度,然后转移,有时却不要。我觉得大概是强行规定了一个点只能经过x次的不要floyd(可能会经过一个点超过x次)没有规定,只要求遍历的最小代价的需要floyd(贪心性质)。
无论是几进制装压,2进制3进制哪怕是k进制都是一样的,只不过2进制可以用位运算简洁实现操作。其他进制可以先拆成数组然后逐位操作。
TSP方程:f[i][j]=min(f[i-(1<<(j-1))][k]+cost[k][j]);
装压DP的细节很多,做题时需要多加注意。
HDU3001---3进制装压TSP,注意不能用floyd!!!
代码:
#include<bits/stdc++.h>
using namespace std;
#define cls(x) memset(x,0,sizeof x)
#define debug(x) cerr<<#x<<"="<<x<<endl
const int maxn = 15;
int f[60000][maxn];
int a[maxn][maxn];
int po[maxn];
int n,m;
int now[maxn];
int t[maxn];
int ans;
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
}
void calc(int p[maxn],int x)
{
int cnt=0;
while(x)
{
cnt++;
p[cnt]=x%3;
x/=3;
}
}
int work(int x[maxn])
{
int num=0;
for(int i=n;i>=1;i--)
num=num*3+x[i];
return num;
}
int check(int x)
{
int w[maxn]={0};
calc(w,x);
for(int i=1;i<=n;i++)
if(w[i]==0) return 0;
return 1;
}
int main()
{
po[0]=1;
for(int i=1;i<=11;i++) po[i]=po[i-1]*3;
while(~scanf("%d%d",&n,&m))
{
ans=0x3f3f3f3f;
memset(a,0x3f,sizeof a);
memset(f,0x3f,sizeof f);
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
a[x][y]=a[y][x]=min(a[x][y],z);
}
//floyd() 注意不能加!
for(int i=1;i<=n;i++)
f[po[i-1]][i]=0;
for(int i=1;i<po[n];i++)
{
cls(now);
calc(now,i);
for(int j=1;j<=n;j++)
{
if(now[j]==0) continue;
cls(t);
memcpy(t,now,sizeof now);
t[j]--;
int state=work(t);
for(int k=1;k<=n;k++)
{
if(now[k]==0||t[k]==0) continue;
if(j==k) continue;
f[i][j]=min(f[i][j],f[state][k]+a[k][j]);
}
}
}
for(int i=1;i<po[n];i++)
if(check(i))
for(int j=1;j<=n;j++)
ans=min(ans,f[i][j]);
if(ans==0x3f3f3f3f) printf("-1\n");
else printf("%d\n",ans);
}
return 0;
}