【题目大意】
给你n个点,m条无向边,每条边有一定的距离数值,构造成一个连通图。问从任意一点出发,遍历所有的边,每条边至少访问一次,再回到起点,求满足要求的方案中走过的距离之和的最小短值。
【分析】
首先想到的是如果这是一个欧拉图,那肯定能经过每条边有且仅有一次,这样的方案一定是最小的(所有边距离的和)。如果不是欧拉图,由于是连通图,根据握手定理,则必有偶数个点的度为奇数。要从一点出发每边至少走一次,则必须要构成一个欧拉回路,所以有些边必须要走多次,每多走一次等价多连接了一条边,这样构成欧拉图,原先的边和新加的虚拟边在欧拉图中有且仅经过一次。现在还要使距离之和最短。原先的边的距离之和是固定的了,要使结果最小,只能使新加的虚拟边之和最小。通过分析可以发现要构成欧拉图,添加的虚拟边的两个端点原先的度数一定是奇数,如果其中有偶数度的点,添加一边后度数就会变奇数,不可能成为欧拉图或者多此一举。于是现在的问题是如何在偶数个奇度顶点中两两连线,使得这些连线的距离之和最小,易想到两个顶点的连线长度应该是这两点间的最短距离(贪心)。想要解决这个问题,可以使用最优匹配算法,也可以使用动态规划。
这里我使用动态规划的方法。
状态表示:
用一串二进制数,第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]为删除的虚拟边的距离)
【代码】
使用压缩状态动归的记忆化搜索算法
1 #include <stdio.h> 2 #include <string.h> 3 const int maxn=0xffffff; 4 int map[20][20]; 5 int du[20],dp[1<<16]; 6 int n,m; 7 int sum; 8 inline int min(int a,int b) 9 { 10 return a>b?b:a; 11 } 12 void insert(int x,int y,int d) 13 { 14 if (d<map[x][y]) map[x][y]=map[y][x]=d; 15 sum+=d; 16 ++du[x]; 17 ++du[y]; 18 } 19 void floyed() 20 { 21 for (int k=0; k<n; ++k) 22 for (int i=0; i<n; ++i) 23 for (int j=0; j<n; ++j) 24 { 25 if (i==j||j==k||i==k) continue; 26 map[i][j]=min(map[i][j],map[i][k]+map[k][j]); 27 } 28 } 29 int search(int st) 30 { 31 if (st==0) return 0; 32 if (dp[st]) return dp[st]; 33 int ans=maxn,tem; 34 for (int i=0; i<n-1; ++i) 35 if (st & (1<<i)) 36 for (int j=i+1; j<n; ++j) 37 { 38 if ((1<<j)&st) 39 { 40 tem=search(st-(1<<i)-(1<<j))+map[i][j]; 41 ans=min(ans,tem); 42 } 43 44 } 45 return dp[st]=ans; 46 } 47 int main() 48 { 49 while (~scanf("%d%d",&n,&m) && n) 50 { 51 sum=0; 52 memset(du,0,sizeof du); 53 memset(dp,0,sizeof dp); 54 for (int i=0; i<n; ++i) 55 for (int j=0; j<n; ++j) map[i][j]=maxn; 56 int x,y,z; 57 while (m--) 58 { 59 scanf("%d%d%d",&x,&y,&z); 60 insert(x-1,y-1,z); 61 } 62 floyed(); 63 int st=0; 64 for (int i=0; i<n; ++i) 65 if (du[i]%2==1) st|=(1<<i); 66 sum+=search(st); 67 printf("%d\n",sum); 68 } 69 70 }