問題描述: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的。