日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Popular Cows POJ - 2186(tarjan算法)+详解

發布時間:2023/12/4 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Popular Cows POJ - 2186(tarjan算法)+详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

題意:

每一頭牛的愿望就是變成一頭最受歡迎的?!,F在有 N頭牛,給你M對整數(A,B),表示牛 A認為牛B受歡迎。這種關系是具有傳遞性的,如果 A認為 B受歡迎, B認為 C受歡迎,那么牛 A也認為牛 C受歡迎。你的任務是求出有多少頭牛被除自己之外的所有牛認為是受歡迎的。

題目:

Every cow’s dream is to become the most popular cow in the herd. In a herd of N (1 <= N <= 10,000) cows, you are given up to M (1 <= M <= 50,000) ordered pairs of the form (A, B) that tell you that cow A thinks that cow B is popular. Since popularity is transitive, if A thinks B is popular and B thinks C is popular, then A will also think that C is
popular, even if this is not explicitly specified by an ordered pair in the input. Your task is to compute the number of cows that are considered popular by every other cow.
Input

  • Line 1: Two space-separated integers, N and M

  • Lines 2…1+M: Two space-separated numbers A and B, meaning that A thinks B is popular.
    Output

  • Line 1: A single integer that is the number of cows who are considered popular by every other cow.

Sample Input

3 3
1 2
2 1
2 3

Sample Output

1

Hint

Cow 3 is the only cow of high popularity.

思路:強連通——tarjan算法

即建立一個搜索樹,運用tarjan算法進行縮點,最終建成一棵新樹。
1.所有點只建成一棵樹(u<=1)。
2.該樹只有兩個結果,即連通(u=1)和不連通(u=0)
3.在ac代碼后,我會詳細介紹tarjan算法。

AC代碼(帶步驟解釋)

#include<stdio.h>//tarjan是一個縮點過程。 #include<string.h> #include<algorithm> using namespace std; const int M=1e4+10; const int N=5e4+10; int to[N],nex[N],fir[M]; int col,num,dfn[M]/*時間戳:標記當前節點在深搜過程中是第幾個遍歷到的點*/; int low[M]/*整個算法核心數組:每個點在這顆樹中的,最小的子樹的根*/; int de[M]/*統計新建樹的入度*/,si[M]/*統計新樹中某節點(強連通分量)內包含多少個點*/; int tot=0,co[M]/*表示新樹的元素*/,n,m; int top,st[M]/*棧*/; void Ins(int x,int y) {to[++tot]=y;nex[tot]=fir[x];///模擬鏈表fir[x]=tot; } void tarjan(int u/*當前節點*/)///tarjan縮點 {dfn[u]=low[u]=++num;//初始化st[++top]=u;//將u節點入棧for(int i=fir[u]; i; i=nex[i]) ///枚舉每一條邊。{int v=to[i]/*其能到達的節點*/;if(!dfn[v])//如果v點未被訪問過{tarjan(v);//繼續往下找low[u]=min(low[u],low[v]);}else if(!co[v]) ///判斷是否在新樹中,若不在需要對改點值更新。low[u]=min(low[u],dfn[v]);}if(low[u]==dfn[u])//某個節點回溯之后的low【u】值還是==dfn【u】的值,那么這個節點無疑就是一個關鍵節點(為強連通分量的一個頂點。){co[u]=++col;/*看做建了一個新樹,只有用強連通分量的頂點建入樹中*/++si[col];/*記錄某連通分量內有多少個點*/while(st[top]!=u)//until u==v;(遍歷該連通分量內有多少個點【在u前的點,均為一個連通分量】){++si[col];co[st[top]]=col;//建立新樹,該聯通分量內均為同一個時間戳。--top;//比此節點后進來的節點全部出棧}--top;//將u退棧。} } int main() {scanf("%d%d",&n,&m);for(int i=1; i<=m; i++){int x,y;scanf("%d%d",&x,&y);Ins(y,x);///反向建邊,統計入度。}for(int i=1; i<=n; i++)if(!dfn[i])tarjan(i);for(int i=1; i<=n; i++)for(int j=fir[i]; j; j=nex[j]) ///統計新樹入度。{int v=to[j]; //i - > vint U=co[i];int V=co[v]; // U -> Vif(U!=V)//前面操作,使得聯通分量內均為同一個時間戳(最小時間戳)de[V]++;}int ans=0,u=0;for(int i=1; i<=col; i++)if(!de[i])//入度不為零ans=si[i],u++;//此時新樹中無連通分量if(u==1)//表明所有牛都歡迎printf("%d\n",ans);//(該新樹聯通分量包含幾個點)else printf("0\n");//u==0,該樹不連通;u>1,有多個樹。return 0; }

tarjan算法詳解(以此題求解過程為例)

不知道怎樣讀tarjan,就去搜了一下,發現發明算法的不是中國人(挺正常?) 發明者:Robert Tarjan,所以我還是老老實實的叫塔尖算法吧。

看他和(殘)藹(酷)的臉,他發明的 tarjan算法,是一個關于圖的聯通性(將強連通分量【一堆點】)縮成一個點)的神奇算法。
基于DFS(迪法師)算法,深度優先搜索一張有向圖。!注意!是有向圖。根據樹,堆棧,領接表等種種神奇方法來完成剖析一個圖的工作。最后經過這些操作建成一個新樹的過程。(縮點過程)思維千絲萬縷,但代碼卻不長(妙哉)?orz廢話不多說,來說下我理解的tarjan(說了,以此題為例)。

首先我們引入定義:

1、有向圖G中,以頂點v為起點的弧的數目稱為v的出度;以頂點v為終點的弧的數目稱為v的入度。
2、如果在有向圖G中,有一條<u,v>有向道路,則v稱為u可達的,或者說,從u可達v。
3、如果有向圖G的任意兩個頂點都互相可達,則稱圖 G是強連通圖,如果有向圖G存在兩頂點u和v使得u不能到v,或者v不能到u,則稱圖G是強非連通圖。
4、如果有向圖G不是強連通圖,他的子圖G2是強連通圖,點v屬于G2,任意包含v的強連通子圖也是G2的子圖,則乘G2是有向圖G的極大強連通子圖,也稱強連通分量。
5、什么是強連通?強連通其實就是指圖中有兩點u,v。使得能夠找到有向路徑從u到v并且也能夠找到有向路徑從v到u,則稱u,v是強連通的。

不妨引入一個圖加強大家對強連通分量和強連通的理解:


標注棕色線條框框的三個部分就分別是一個強連通分量,也就是說,這個圖中的強連通分量有3個。
其中我們分析最左邊三個點的這部分:
其中1能夠到達0,0也能夠通過經過2的路徑到達1.1和0就是強連通的。
其中1能夠通過0到達2,2也能夠到達1,那么1和2就是強連通的。

同理,我們能夠看得出來這一部分確實是強連通分量,也就是說,強連通分量里邊的任意兩個點,都是互相可達的。
那么如何求強連通分量的個數呢?另外強連通算法能夠實現什么一些基本操作呢?

即如何用Tarjan算法求強連通分量個數:

先來段偽代碼
tarjan官方偽代碼如下:

//parent為并查集,FIND為并查集的查找操作

//QUERY為詢問結點對集合

//TREE為基圖有根樹

Tarjan(u)

visit[u] = true

for each (u, v) in QUERY

if visit[v]

ans(u, v) = FIND(v)

for each (u, v) in TREE

if !visit[v]

Tarjan(v)

parent[v] = u

本題偽代碼
tarjan(u){DFN[u]=Low[u]=++Index // 為節點u設定次序編號和Low初值Stack.push(u) // 將節點u壓入棧中for each (u, v) in E // 枚舉每一條邊if (v is not visted) // 如果節點v未被訪問過tarjan(v) // 繼續向下找Low[u] = min(Low[u], Low[v])else if (v in S) // 如果節點u還在棧內Low[u] = min(Low[u], DFN[v])if (DFN[u] == Low[u]) // 如果節點u是強連通分量的根repeat v = S.pop // 將v退棧,為該強連通分量中一個頂點print vuntil (u== v)}

Tarjan算法,是一個基于Dfs的算法,假設我們要先從0號節點開始Dfs,我們發現一次Dfs我萌就能遍歷整個圖(樹),而且我們發現,在Dfs的過程中,我們深搜到 了其他強連通分量中,那么如何判斷哪個和那些節點屬于一個強連通分量呢?我們首先引入兩個數組:

①dfn[ ]
②low[ ]

第一個數組dfn我們用來標記時間戳:當前節點在深搜過程中是第幾個遍歷到的點
第二個數組是整個算法核心數組:經過運算后在這顆樹中的,強連通分量內的的點的low【u】值均為強連通分量頂點的時間戳(以判斷low【u】的值是否一樣來判斷是否為同一個強連通變量)
這個時候我們不妨在紙上畫一畫寫一寫,搞出隨意一個Dfs出來的dfn數組來觀察一下(假設我們從節點0開始的Dfs,其中一種可能的結果是這樣滴):

這個時候我們回頭來看第二個數組要怎樣操作,我們定義low【u】=min(low【u】,low【v】(即使v搜過了也要進行這步操作,但是v一定要在棧內才行)),

  • (1) u代表當前節點,v代表其能到達的節點。

  • (2)這個數組在剛剛到達節點u的時候初始化: low【u】=dfn【u】。

  • (3)然后在進行下一層深搜之后回溯回來的時候,維護low【u】。
    1)如果如果v點未被訪問過Low[u] = min(Low[u], Low[v])
    2)如果如果v點被訪問過,且未被建在新樹中。

  • (4)如果我們發現了某個節點回溯之后的**low【u】值==dfn【u]**的值,那么這個節點無疑就是一個關鍵節點(為強連通分量的一個頂點):從這個節點能夠到達其強連通分量中的其他節點,但是沒有其他屬于這個強連通分量以外的點能夠到達這個點,所以這個點的low【u】值維護完了之后還是和dfn【u】的值。

上圖運行一遍的各個數值的變化。

①首先進入0號節點,初始化其low【0】=dfn【0】=1,然后深搜到節點2,初始化其:low【2】=dfn【2】=2,然后深搜到節點1,初始化其:low【1】=dfn【1】=3,將其全部進棧;

②然后從節點1開始繼續深搜,發現0號節點已經搜過了,沒有繼續能夠搜的點了,開始回溯維護其值。low【1】=min(low【1】,low【0】)=1;low【2】=min(low【2】,low【1】)=1;low【0】=min(low【0】,low【2】)=1;

③這個時候雖然low【0】==dfn【0】,但不能斷定0號節點是一個關鍵點,別忘了,這個時候還有3號節點沒有遍歷,我們只有在其能夠到達的節點全部判斷完之后,才能夠下結論,所以我們繼續Dfs。

④繼續深搜到3號節點,初始化其low【3】=dfn【3】=4,然后深搜到4號節點,初始化其:low【4】=dfn【4】=5,將兩個點都進棧。這個時候發現深搜到底,回溯,因為節點4沒有能夠到達的點,所以low【4】也就沒有幸進行維護即:low【4】=dfn【4】出棧,進入新樹co[4]=1(這個點一定是強連通分量的關鍵點,但是我們先忽略這個點,這個點沒有代表性,一會分析關鍵點的問題),然后回溯到3號節點,low【3】=min(low【3】,low【4】)=4;(由于連通性質,回溯時,low【3】的時間戳一定小于low【4】,low【3】是一個關鍵點,不會改變)發現low【3】==dfn【3】出棧,co[3]=2(那么這個點也是個關鍵點,我們同樣忽略掉。)

⑤最終回溯到節點0,進行最后一次值的維護:low【0】=min(low【0】,low【3】)=0,這個時候我們猛然發現其dfn【0】==low【0】,根據剛才所述,那么這個點就是一個關鍵點:能夠遍歷其屬強連通分量的點的起始點,而且沒有其他點屬于其他強連通分量能夠有一條有向路徑連到這個節點來的節點,此時棧頂元素為2,則棧內在0上的所有點包括零都為同一個強連通分量,出棧進入新樹co[2]=3,co[1]=3,co[0]=3.

大家仔細理解一下這句話,因為這個點屬于一個強連通分量,而且強連通分量中的任意兩個節點都是互達的,也就是說強連通分量中一定存在環,這個最后能夠回到0號節點的1號節點一定有機會維護low【1】,因為0號節點是先進來的時間戳一定小,所以其low【1】的值也一定會跟著變小,然后在回溯的過程中,其屬一個強連通分量的所有點都會將low【u】值維護成low【0】,所以這個0號節點就是這個關鍵點:能夠遍歷其屬強連通分量的起始點而且這樣的起始點一定只有一個,所以只要發現了一個這樣的關鍵起始點,那么就一定發現了一個強連通分量。而且這個節點沒有其他點屬于其他強連通分量能夠有一條有向路徑連到這個節點來的節點:如果這樣的點存在,那么這些個點應該屬于同一個強連通分量。

那么綜上所述,相信大家也就能夠理解為什么dfn【u】==low【u】的時候,我們就可以判斷我們發現了一個強連通分量了。

總結

以上是生活随笔為你收集整理的Popular Cows POJ - 2186(tarjan算法)+详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。