poj 2404 floyd+状态压缩(中国邮递员问题)

题意:经典中国邮递员问题。给定一个连通图,顶点之间可能有若干条边,要求从任意一点出发,遍历所有的边,每条边至少访问一次,再回到起点。求满足要求的方案中走过的距离之和的最小值。

思路(http://www.cnblogs.com/wuminye/archive/2013/05/06/3063902.html):

首先想到的是如果这是一个欧拉图,那肯定能经过每条边有且仅有一次,这样的方案一定是最小的(所有边距离的和)。如果不是欧拉图,由于是连通图,根据握手定理,则必有偶数个点的度为奇数。要从一点出发每边至少走一次,则必须要构成一个欧拉回路,所以有些边必须要走多次,每多走一次等价多连接了一条边,这样构成欧拉图,原先的边和新加的虚拟边在欧拉图中有且仅经过一次。现在还要使距离之和最短。原先的边的距离之和是固定的了,要使结果最小,只能使新加的虚拟边之和最小。通过分析可以发现要构成欧拉图,添加的虚拟边的两个端点原先的度数一定是奇数,如果其中有偶数度的点,添加一边后度数就会变奇数,不可能成为欧拉图或者多此一举。于是现在的问题是如何在偶数个奇度顶点中两两连线,使得这些连线的距离之和最小,易想到两个顶点的连线长度应该是这两点间的最短距离(贪心)。想要解决这个问题,可以使用最优匹配算法,也可以使用动态规划。

这里我使用动态规划的方法。

状态表示:

用一串二进制数,第i位数表示第i个点是否为奇度点,0表示不是,1表示是。例如00110101表示1、3、5、6点的度数为奇数。
每个状态划分为一个阶段。

阶段状态转移:

每个状态可以从当前状态任意使两个1变为0 的状态转移而来,也就是说从删除一条边变为当前状态的状态转移而来。
比如说00110101可以从6个状态转移而来:00000101、00110000、00010001、00100001、00010100、00100100

无后效应:

如果当前状态是通过之前的一条转移路径转移而来,不会导致之后有些本该转移的状态不可转移。
例如:当前为00110101,无论之前如何转移,之后一定可以转移成00111111

最优子结构:

当前状态储存的值为在当前状态的情况下所需要的最少距离,这个值的转移方程为:

f[cur]=min{f[pre]+dis[pre][cur]} (要求:pre状态可以转移到cur状态,dis[pre][cur]为删除的虚拟边的距离


#include <stdio.h>
#include <string.h>
#define INF 0x3fffffff
#define min(a,b) ((a)<(b)?(a):(b))
#define N 20
#define M 15
int g[N][N],dp[1<<M],d[N];
int n,m,sum;
void init(){
	int i,j;
	sum = 0;
	memset(dp,0,sizeof(dp));
	memset(d,0,sizeof(d));
	for(i = 0;i<N;i++)
		for(j = 0;j<N;j++)
			g[i][j] = INF;
}
void add(int x,int y,int w){
	if(w < g[x][y])//边只保存最小的,以便floyd求出最短路径
		g[x][y] = g[y][x] = w;
	sum += w;
	d[x]++;
	d[y]++;
}
void floyd(){
	int i,j,k;
	for(k = 1;k<=n;k++)
		for(i = 1;i<=n;i++)
			for(j = 1;j<=n;j++)
				g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
}
int test(int x){
	int i,j;
	int ans = INF,temp;
	if(dp[x] || !x)
		return dp[x];
	for(i = 0;i<n-1;i++)//遍历压缩状态中的所有'1'对,即所有的奇点对
		if((1<<i) & x)
			for(j = i+1;j<n;j++)
				if((1<<j) & x){
					temp = x^(1<<i)^(1<<j);
					ans = min(ans,test(temp)+g[i+1][j+1]);
				}
	return dp[x] = ans;
}
int main(){
	freopen("a.txt","r",stdin);
	while(scanf("%d",&n) && n){
		int i,a,b,w;
		init();
		scanf("%d",&m);
		for(i = 0;i<m;i++){
			scanf("%d %d %d",&a,&b,&w);
			add(a,b,w);
		}
		floyd();
		for(i = n,a = 0;i>=1;i--){
			a <<= 1;
			if(d[i]&1)
				a++;
		}
		sum += test(a);
		printf("%d\n",sum);
	}
	return 0;
}



你可能感兴趣的:(poj 2404 floyd+状态压缩(中国邮递员问题))