信息传递
noip2015d1t2。
原題鏈接:https://www.luogu.org/problem/show?pid=2661
這道題其實是讓你求一個圖中最小環的長度。正常向方法是搜索+剪枝,不過也有一個叫tarjan的算法,可以用來求強聯通分量。
兩個方法我都說一下吧。
正常向方法:
1.建圖,這里一般用鄰接表,當然不用也行,因為就這個題來說沒必要記錄這么多信息,只記錄某個點的連接的下一個點的編號就行了,我這里就沒寫正常鄰接表。
2.以每個點為起點進行一次DFS。這里有一步判環操作:打標記,也就是所謂的時間戳,判環用的。每次擴展節點的時候查看一下這個點是不是有時間戳,如果有那就說明這個點曾經擴展過,那就是成環了。把以所有點出發的環的長度求出來,取一個最小值就是答案。應該需要隊列。
(我要是沒記錯的話STL隊列會T,所以建議手寫隊列)
我使用了拓撲排序,顯然題中的這個圖一定是個DAG,所以在拓撲排序的時候,我們要不斷地找入邊,這樣可以刪除不成環的邊,刪完后就剩下一個個的環。
再在這些環上用DFS求最小環長度就好了。
?
第二種方法是用tarjan算法求強聯通分量。這個算法是由一個名叫tarjan的人提出來的,由他的名字命名 (但這個詞到底怎么讀,我遇到過好多版本的)。。
先說一下什么叫強聯通分量。如果兩個頂點可以相互通達,則稱兩個頂點強連通。如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。有向圖的極大強連通子圖,稱為強連通分量。
tarjan算法可以在線性時間復雜度內求出一個有向圖的強聯通分量。
Tarjan算法是基于對圖深度優先搜索的算法,每個強連通分量為搜索樹中的一棵子樹。
搜索時,把當前搜索樹中未處理的節點放到一個棧里面,在回溯的時候就可以判斷棧頂到棧中的節點是否為一個強連通分量。
為了方便,我們定義(好多文章中都是以這個名字定義的,我也就沿用了):
dfn[ i ] 為在DFS中該節點被搜索的次序,也就是時間戳。
low[ i ] : 為i或i的子樹能夠追溯到的最早的棧中節點的次序號。
可以證明,當dfn[ i ]=low[ i ]時,i或i的子樹可以構成一個強連通分量。
具體演示過程,可以參考這位BYVoid博主寫的博文:https://www.byvoid.com/zhs/blog/scc-tarjan
這里給出分別使用兩種方法的AC代碼:
1.正常向:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cstdlib> 5 #define maxn 200005 6 #define INF 1e9+7; 7 using namespace std; 8 int n; 9 int d[maxn],vis[maxn],to[maxn],q[maxn]; 10 /* 11 d數組記錄某個點的入度 12 vis數組便是標記數組 13 to數組記錄從某個點出發指向哪里,因為按照題意每個點的目標只有一個 14 q為一般隊列 15 */ 16 int ans=INF; 17 inline int read(){ 18 int num = 0; 19 char c; 20 bool flag = false; 21 while ((c = getchar()) == ' ' || c == '\n' || c == '\r'); 22 if (c == '-') flag = true; 23 else 24 num = c - '0'; 25 while (isdigit(c = getchar())) 26 num = num * 10 + c - '0'; 27 return (flag ? -1 : 1) * num; 28 } 29 void tppx(void){ 30 int head=0; 31 int tail=0; 32 for (int i=1;i<=n;i++)//拓撲排序先標記一下入度為0的點 33 if (d[i] == 0){ 34 vis[i] = 1; 35 q[++tail] = i; 36 } 37 while (head<tail){//從入度為0的點開始BFS 38 int now=q[++head];//取隊首并彈出隊首,簡化寫法,以下同 39 d[to[now]]--;//每次取了一條邊也可看作刪掉了這條邊,入度-1 40 if(d[to[now]]==0){//如果下一個連接的點也入度為0了,那么再以這個點進行BFS(想一下拓撲排序的主過程) 41 vis[to[now]] = 1; 42 q[++tail] = to[now]; 43 } 44 } 45 } 46 void dfs(int now,int k){//以每個點為起點遍歷整個圖,k用來記錄當前的長度,求一個最小的ans就是答案 47 vis[now] = 1; 48 if (vis[to[now]]==0) 49 dfs(to[now],k+1); 50 else 51 ans = min(ans,k); 52 53 } 54 int main(){//主過程就很明顯了吧 55 n = read(); 56 for (int i=1;i<=n;i++){ 57 to[i] = read(); 58 d[to[i]]++; 59 } 60 tppx(); 61 for (int i=1;i<=n;i++){ 62 if (!vis[i]) 63 dfs(i,1); 64 } 65 cout << ans << endl; 66 return 0; 67 }
2.使用tarjan算法:
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define maxn 200005 6 #define INF 1e9+7; 7 using namespace std; 8 int n; 9 int ans=INF; 10 int s[maxn],top=0; 11 int dfn[maxn],low[maxn],tot=0; 12 int head[maxn],cnt=0; 13 int vis[maxn]; 14 struct Edge{//這里我用了正常鄰接表 15 int from; 16 int to; 17 }; 18 Edge edge[maxn]; 19 inline int read(){//到哪都少不了的快讀 20 int num = 0; 21 char c; 22 bool flag = false; 23 while ((c = getchar()) == ' ' || c == '\n' || c == '\r'); 24 if (c == '-') flag = true; 25 else 26 num = c - '0'; 27 while (isdigit(c = getchar())) 28 num = num * 10 + c - '0'; 29 return (flag ? -1 : 1) * num; 30 } 31 void add_edge(int from,int to){ 32 edge[++cnt].from=head[from]; 33 edge[cnt].to = to; 34 head[from]=cnt; 35 } 36 void tarjan(int u){ 37 vis[u]=1; 38 dfn[u]=low[u]=++tot; 39 s[++top]=u; 40 for(int i=head[u];i!=0;i=edge[i].from){ 41 int v = edge[i].to; 42 if(!dfn[v]){ 43 tarjan(v); 44 low[u]=min(low[u],low[v]); 45 } 46 else if(vis[v]) 47 low[u]=min(low[u],dfn[v]); 48 } 49 if(low[u]==dfn[u]){ 50 int temp=0; 51 do{ 52 vis[s[top]]=0; 53 temp++; 54 }while(s[top--] != u); 55 if(temp!=1) 56 ans=min(ans,temp); 57 } 58 } 59 int main(){ 60 n = read(); 61 for(int u=1;u<=n;u++){ 62 int v; 63 v = read(); 64 add_edge(u,v); 65 } 66 for(int i=1;i<=n;i++)//依然是每個點都要考慮,從每個點出發求一遍連通分量取最小值 67 if(!dfn[i]) 68 tarjan(i); 69 cout << ans << endl; 70 return 0; 71 }
?
轉載于:https://www.cnblogs.com/OIerShawnZhou/p/7451095.html
總結
- 上一篇: C#语法基础之第三节
- 下一篇: 游戏服某个服外网玩家连不上,内网才能连