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