1. 程式人生 > >CCF201709-4通訊網路_雙向DFS

CCF201709-4通訊網路_雙向DFS

這題一看, 如果不考慮時間的話, 用floyd演算法求傳遞閉包和對稱閉包是可以解決的, 但是題目的資料只夠過60分, 正解顯然不是這樣.

然後我又思考, 開始往復雜了想, 於是有了下面的不知對錯的思路:

先判斷圖是否連通,否則結果為0
用tarjan演算法縮點, 把環全都合成一個結點,他們的處境是一樣的
找到一個入度為0的結點,(縮點之後一定存在), 它會指向好幾個點, 找出它指向點的共同點, 不斷找直到共同指向
一個點. 尋找過程中每個唯一的聚合點就是結果點

不僅實現起來難, 而且思路是否正確還存疑. 畢竟我還沒有掌握強連通分量縮點的演算法.

然後投降了, 找找部落格吧, 找到的第一篇部落格居然就用了縮點的演算法! 然後看其他部落格, 發現他們的共同點都是用了DFS.

WTF? 這題能用DFS? 不會超時嗎? 於是我開始考慮DFS, 確實不會超時, 時間複雜度是O(NM).

這裡暴露出我的一個問題, 我的潛意識裡面認為DFS就是遞迴嘛, 遞迴都是很慢的嘛, 所以直接用DFS很容易超時的嘛. 然而並不! 所以遇到問題還是應該具體分析下時間, 不要被自己的潛意識誤導, 從而錯過正確的思路.

下面是DFS思路, 準確來說是雙向DFS
  • 在有向圖中, 對於一個點, 它的DFS遍歷可達的點是它可傳遞資訊的點.
  • 它的反向DFS遍歷(邊的方向反轉)可達的點是可傳遞資訊到它的點.
  • 那麼我們獲取這兩種點, 如果這兩種點集包含除自己以外的所有點, 那麼答案+1.

就是這麼簡單的思路, 雙向DFS, 找到可傳遞資訊的所有點. 需要注意的是, 這裡不應該在遍歷的過程中找到點就cnt++, 因為前後遍歷可能會出現重複的點(雙向), 就會出錯. 正確方法應該是記錄這些可達的點, 然後用hash的方法去統計.

最後此題學到的一個小經驗就是存圖的時候還是儘量用鄰接表吧, 用起來也方便, 而且不容易超時! 不容易超時! 不容易超時!
100分程式碼

#include <algorithm>
#include <iostream>
#include <vector>
#include <cstring>
using
namespace std; const int maxn = 1005; vector<int> Gp[maxn], Gs[maxn], to[maxn]; int n, m, vis[maxn], ans = 0; void DFS(vector<int> G[maxn], int u, int scr) { for (int i = 0; i < G[u].size(); ++i) { if (!vis[G[u][i]]) { vis[G[u][i]] = true; DFS(G, G[u][i], scr); } } to[scr].push_back(u); } int main() { cin >> n >> m; for (int i = 0, u, v; i < m; ++i) { cin >> u >> v; Gp[u - 1].push_back(v - 1); Gs[v - 1].push_back(u - 1); } for (int i = 0; i < n; ++i) { memset(vis, 0, sizeof(vis)); DFS(Gp, i, i); } for (int i = 0; i < n; ++i) { memset(vis, 0, sizeof(vis)); DFS(Gs, i, i); } for (int i = 0; i < n; ++i) { memset(vis, 0, sizeof(vis)); for (int j = 0; j < to[i].size(); ++j) { vis[to[i][j]] = 1; } bool flag = false; for (int j = 0; j < n; ++j) { if (vis[j] == 0) flag = true; } if (flag == false) ans++; } cout << ans; }