信息传递
noip2015d1t2。
原題鏈接:https://www.luogu.org/problem/show?pid=2661
這道題其實(shí)是讓你求一個(gè)圖中最小環(huán)的長度。正常向方法是搜索+剪枝,不過也有一個(gè)叫tarjan的算法,可以用來求強(qiáng)聯(lián)通分量。
兩個(gè)方法我都說一下吧。
正常向方法:
1.建圖,這里一般用鄰接表,當(dāng)然不用也行,因?yàn)榫瓦@個(gè)題來說沒必要記錄這么多信息,只記錄某個(gè)點(diǎn)的連接的下一個(gè)點(diǎn)的編號(hào)就行了,我這里就沒寫正常鄰接表。
2.以每個(gè)點(diǎn)為起點(diǎn)進(jìn)行一次DFS。這里有一步判環(huán)操作:打標(biāo)記,也就是所謂的時(shí)間戳,判環(huán)用的。每次擴(kuò)展節(jié)點(diǎn)的時(shí)候查看一下這個(gè)點(diǎn)是不是有時(shí)間戳,如果有那就說明這個(gè)點(diǎn)曾經(jīng)擴(kuò)展過,那就是成環(huán)了。把以所有點(diǎn)出發(fā)的環(huán)的長度求出來,取一個(gè)最小值就是答案。應(yīng)該需要隊(duì)列。
(我要是沒記錯(cuò)的話STL隊(duì)列會(huì)T,所以建議手寫隊(duì)列)
我使用了拓?fù)渑判?#xff0c;顯然題中的這個(gè)圖一定是個(gè)DAG,所以在拓?fù)渑判虻臅r(shí)候,我們要不斷地找入邊,這樣可以刪除不成環(huán)的邊,刪完后就剩下一個(gè)個(gè)的環(huán)。
再在這些環(huán)上用DFS求最小環(huán)長度就好了。
?
第二種方法是用tarjan算法求強(qiáng)聯(lián)通分量。這個(gè)算法是由一個(gè)名叫tarjan的人提出來的,由他的名字命名 (但這個(gè)詞到底怎么讀,我遇到過好多版本的)。。
先說一下什么叫強(qiáng)聯(lián)通分量。如果兩個(gè)頂點(diǎn)可以相互通達(dá),則稱兩個(gè)頂點(diǎn)強(qiáng)連通。如果有向圖G的每兩個(gè)頂點(diǎn)都強(qiáng)連通,稱G是一個(gè)強(qiáng)連通圖。有向圖的極大強(qiáng)連通子圖,稱為強(qiáng)連通分量。
tarjan算法可以在線性時(shí)間復(fù)雜度內(nèi)求出一個(gè)有向圖的強(qiáng)聯(lián)通分量。
Tarjan算法是基于對(duì)圖深度優(yōu)先搜索的算法,每個(gè)強(qiáng)連通分量為搜索樹中的一棵子樹。
搜索時(shí),把當(dāng)前搜索樹中未處理的節(jié)點(diǎn)放到一個(gè)棧里面,在回溯的時(shí)候就可以判斷棧頂?shù)綏V械墓?jié)點(diǎn)是否為一個(gè)強(qiáng)連通分量。
為了方便,我們定義(好多文章中都是以這個(gè)名字定義的,我也就沿用了):
dfn[ i ] 為在DFS中該節(jié)點(diǎn)被搜索的次序,也就是時(shí)間戳。
low[ i ] : 為i或i的子樹能夠追溯到的最早的棧中節(jié)點(diǎn)的次序號(hào)。
可以證明,當(dāng)dfn[ i ]=low[ i ]時(shí),i或i的子樹可以構(gòu)成一個(gè)強(qiáng)連通分量。
具體演示過程,可以參考這位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數(shù)組記錄某個(gè)點(diǎn)的入度 12 vis數(shù)組便是標(biāo)記數(shù)組 13 to數(shù)組記錄從某個(gè)點(diǎn)出發(fā)指向哪里,因?yàn)榘凑疹}意每個(gè)點(diǎn)的目標(biāo)只有一個(gè) 14 q為一般隊(duì)列 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++)//拓?fù)渑判蛳葮?biāo)記一下入度為0的點(diǎn) 33 if (d[i] == 0){ 34 vis[i] = 1; 35 q[++tail] = i; 36 } 37 while (head<tail){//從入度為0的點(diǎn)開始BFS 38 int now=q[++head];//取隊(duì)首并彈出隊(duì)首,簡化寫法,以下同 39 d[to[now]]--;//每次取了一條邊也可看作刪掉了這條邊,入度-1 40 if(d[to[now]]==0){//如果下一個(gè)連接的點(diǎn)也入度為0了,那么再以這個(gè)點(diǎn)進(jìn)行BFS(想一下拓?fù)渑判虻闹鬟^程) 41 vis[to[now]] = 1; 42 q[++tail] = to[now]; 43 } 44 } 45 } 46 void dfs(int now,int k){//以每個(gè)點(diǎn)為起點(diǎn)遍歷整個(gè)圖,k用來記錄當(dāng)前的長度,求一個(gè)最小的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++)//依然是每個(gè)點(diǎn)都要考慮,從每個(gè)點(diǎn)出發(fā)求一遍連通分量取最小值 67 if(!dfn[i]) 68 tarjan(i); 69 cout << ans << endl; 70 return 0; 71 }
?
轉(zhuǎn)載于:https://www.cnblogs.com/OIerShawnZhou/p/7451095.html
總結(jié)
- 上一篇: C#语法基础之第三节
- 下一篇: 游戏服某个服外网玩家连不上,内网才能连