问题描述:Chinese poster-man problem,简称CPP,给出一张连通图,问经过每条边至少一次且起点和终点相同,所需走的最小路程。
无向图CPP
1.考虑当所有点度数均为偶数时,该图是欧拉图,因此任意一条欧拉回路都是答案
2.当有两个点是奇度点的时候,只需找到这两点间的最短路径,将最短路径上的边计入到原图中,这是得到了一张欧拉图(poj 1237 The Postal Worker Rings Once)
3.当奇度点个数大于2时,需要使每个点的度数均变为偶数,即在2n个点之间连n条路,于是变为了无向图的最小匹配问题(KM算法时错误的,因为无法保证解的正确性,带花树开花算法是正确的),一般采用状压dp解决该问题。(poj 2404 Jogging Trails)
对于每个奇度点,用一个二进制位表示,1代表尚未匹配,0代表已经匹配,寻找当前状态下尚未匹配的两点匹配同时更新状态,然后利用记忆化搜索寻找最优解。
import java.io.IOException; import java.util.Arrays; import java.util.Scanner; public class Main { int dgr[] = new int[20]; int map[][] = new int[20][20], inf = 1 << 28; Scanner scan = new Scanner(System.in); void floyd(int n) { for (int k = 1; k <=n; k++) for (int i = 1; i <=n; i++) for (int j = 1; j <=n; j++) map[i][j] = Math.min(map[i][j], map[i][k] + map[k][j]); } int stack[]=new int[20],top; int dp[]=new int[1<<17]; int dfs(int s){ if(s==0) return 0; if(dp[s]!=-1) return dp[s]; int res=inf,i=0; while((s&(1<<i))==0) i++; for(int j=i+1;j<top;j++) if((s&(1<<j))!=0){ int temp=dfs(s-(1<<i)-(1<<j))+map[stack[i]][stack[j]]; res=Math.min(res, temp); } return dp[s]=res; } void run() { while (true) { int n = scan.nextInt(); if (n == 0) break; int m = scan.nextInt(); Arrays.fill(dgr, 0); for (int i = 0; i < 20; i++) Arrays.fill(map[i], inf); int sum = 0; while (m-- > 0) { int a = scan.nextInt(); int b = scan.nextInt(); int c = scan.nextInt(); map[b][a]=map[a][b] = Math.min(map[a][b], c); sum += c; dgr[a]++; dgr[b]++; } floyd(n); top=0; for(int i=1;i<=n;i++) if(dgr[i]%2==1) stack[top++]=i; Arrays.fill(dp, -1); sum+=dfs((1<<top)-1); System.out.println(sum); } } public static void main(String[] args) throws IOException { new Main().run(); } }
有向图CPP:
与无向图相同,都是试图用最小花费构造欧拉路,首先计算每个点的出度和入度的差d(v),然后利用最小费用流求解即可,构图如下:
1 其顶点集为图G 的所有顶点,以及附加的超级源s和超级汇t ;
2 对于图G 中每一条边(u ,v ),在N 中连边(u ,v ),容量为inf,费用为该边的长度;
3 从源点s 向所有d (v) <0的顶点v 连边(s,v),容量为-d (v),费用为0;
4 从所有d (v) > 0的顶点u 向汇点t 连边(u ,t),容量为d (v ),费用为0。
混合图CPP:
如果部分街道能够双向通行,部分街道只能单向通行。这个问题已被证明是NPC的。