USACO Riding The Fences 與歐拉路徑問題

簡單的看,圖的路徑算法可以分兩類:

  1. 可達性尋問題:找到一條滿足某種條件的路徑,如圖的連通性問題(簡單路徑算法),歐拉路徑,漢密頓路徑等等;
  2. 含權圖的最優化問題:如點對間的最短路徑,歐幾里德網;

而一切和圖有關的算法,幾乎都是DFS和BFS,這兩傢伙乍一看濃眉大眼的。他們所反映的,是計算機中的邏輯性與次序性,世界紛繁複雜,DFS與BFS是探索這些雜亂無章的一種邏輯很清晰、次序性很明顯的思維方式。我當時學習圖的時候,第一感覺就是亂糟糟噁心的一拖東西堆着,哪像樹呢?多好看?爲什麼好看呢?因爲好理解,不斷的分叉下去,是吧,規律性很明顯!在Wikipedia上找到了兩張圖,可以很好的說明思維的次序性問題:



好了,回到fence問題上來,歐拉路徑的存在性問題已經有了結論了,請見Robert Sedgewick的《Algorithms in C++》,結論是這樣的:

  • 對於一個無向圖,如果它每個點的度都是偶數,那麼它存在一條歐拉回路;
  • 如果有且僅有2個點的度爲奇數,那麼它存在一條歐拉路;
  • 如果超過2個點的度爲奇數,那麼它就不存在歐拉路了。

找出歐拉路的方法就是採用DFS的方式,對於當前的點,把所有點從小到大的搜索,找到和它相連的,找到一個之後刪除它們之間的連線,並去搜索新的那個點,如果沒有找到點和它相連,那麼就把這個點加入輸出隊列。

以上就是這個題的主題思路了,但是僅僅這麼做會超時的。前面說:世界紛繁複雜,DFS與BFS是探索這些雜亂無章的一種邏輯很清晰、次序性很明顯的思維方式”,現在,有了這個思維的主線條了,接下來就是挖掘其中的細節,也就是剪枝。太多的剪枝我也沒發現很多,不過有一點是,前面我們說,找到和它相連的,找到一個之後刪除它們之間的連線”,這裏是會產生一個問題的,那就是有可能刪除了一些要命的邊,使得圖不連通了:


像上面的,中間那個黃點點掛了,要是左或右還沒走完,整個圖怎麼都走不出一條歐拉回路了。當然圖畫的有點醜,呵呵~

所以這裏又有些考察基礎了。Robert的書裏有過幾個概念:

割點:若一個點刪除後(也就是與之相連的邊統統去掉),無向圖不再連通,那麼此點稱爲割點。
橋:若一條邊斷去後,無向圖不再連通,那麼此邊稱爲橋。橋有一個很好的性質,就是DFS一個無向圖,那麼這個過程必定要經過橋。
塊:沒有割點的無向圖稱爲2-連通分支,也稱作塊。

現在應該比較清楚了,也就是說,在進行DFS搜索前,可以先用FloodFill灌水算法判斷一下圖的連通性,FloodFill算是OIer菜譜上的算法了吧,所以還是基礎啊。。。

BTW,FloodFill不一定就是遞歸的,下面的代碼中用的就是一個循環。

  1. /*
  2. ID:fairyroad
  3. LANG:C++
  4. TASK:fence
  5. */
  6. #include <fstream>
  7. #include <vector>
  8. #include <cstring>
  9. using namespace std;
  10.  
  11. ifstream fin("fence.in");
  12. ofstream fout("fence.out");
  13.  
  14. const int MAX = 505;
  15. int edge[MAX][MAX];
  16. int nodeCnt[MAX];
  17. bool visited[MAX];
  18. int F;
  19.  
  20. vector<int> ans;
  21.  
  22. bool floodFill(int node)
  23. {
  24.     memset(visited, 0sizeof(visited));
  25.     visited[node] = true;
  26.  
  27.     vector<int> Q;
  28.     Q.push_back(node);
  29.  
  30.     while(!Q.empty())
  31.     {
  32.         node = Q.back();
  33.         Q.pop_back();
  34.         for (int i = 0; i < MAX; ++i)
  35.         {
  36.             if(edge[node][i] && !visited[i])
  37.             {
  38.                 Q.push_back(i);
  39.                 visited[i] = true;
  40.             }
  41.         }
  42.     }
  43.  
  44.     for (int i = 1; i < MAX; ++i)
  45.     {
  46.         if (!visited[i] && nodeCnt[i])
  47.         {
  48.             return false;
  49.         }
  50.     }
  51.  
  52.     return true;
  53. }
  54.  
  55. bool dfs(int curr)
  56. {
  57.     if((int)ans.size() == F+1)
  58.     {
  59.         for(size_t i = 0; i < ans.size(); ++i)
  60.             fout << ans[i] << endl;
  61.         //exit(0);
  62.         return true;
  63.     }
  64.  
  65.     if(!floodFill(curr))
  66.         return false;
  67.  
  68.     for(int i = 1; i < MAX; ++i)
  69.     {
  70.         if(edge[curr][i])
  71.         {
  72.             ans.push_back(i);
  73.             --edge[curr][i]; --edge[i][curr];
  74.             --nodeCnt[curr]; --nodeCnt[i];
  75.  
  76.             if(dfs(i))
  77.                 return true;
  78.  
  79.             ans.pop_back();
  80.             ++edge[curr][i]; ++edge[i][curr];
  81.             ++nodeCnt[curr]; ++nodeCnt[i];
  82.         }
  83.     }
  84.  
  85.     return false;
  86. }
  87.  
  88. int main()
  89. {
  90.     fin >> F;
  91.     int i, v1, v2;
  92.     for(= 0; i < F; ++i)
  93.     {
  94.         fin >> v1 >> v2;
  95.         ++edge[v1][v2]; ++edge[v2][v1];
  96.         ++nodeCnt[v1]; ++nodeCnt[v2];
  97.     }
  98.  
  99.     int start = -1, oddNode = 0;
  100.     for(int i = 0; i < MAX; ++i)
  101.     {
  102.         if(nodeCnt[i] & 0x1u)
  103.         {
  104.             ++oddNode;
  105.             if(start == -1) start = i;
  106.         }
  107.     }
  108.  
  109.     if(oddNode != 2) // 題目保證了有解
  110.         start = 1;
  111.  
  112.     ans.push_back(start);
  113.     dfs(start);
  114.  
  115.     return 0;
  116. }


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章