USACO Riding The Fences 与欧拉路径问题

本文详细探讨了欧拉路径存在的条件及其求解方法,重点介绍了使用深度优先搜索(DFS)来寻找欧拉路径的过程,并通过剪枝技巧提高算法效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简单的看,图的路径算法可以分两类:

  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. }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值