装压DP及TSP问题的学习

当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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值