wikioi 2822 愛在心中

2022-05-12 02:56:57 字數 3788 閱讀 9567

題目鏈結

演算法:tarjan+dfs(最短路的都行,判連通而已)

先了解一下什麼是tarjan

tarjan演算法用於求出圖中所有的強連通分量。

**nocow:點選開啟鏈結

在有向圖g中,如果兩個頂點間至少存在一條路徑,稱兩個頂點強連通(strongly connected)。如果有向圖g的每兩個頂點都強連通,稱g是乙個強連通圖。非強連通圖有向圖的極大強連通子圖,稱為強連通分量(strongly connected components)。

tarjan演算法是基於對圖深度優先搜尋的演算法,每個強連通分量為搜尋樹中的一棵子樹。搜尋時,把當前搜尋樹中未處理的節點加入乙個堆疊,回溯時可以判斷棧頂到棧中的節點是否為乙個強連通分量。

定義dfn(u)為節點u搜尋的次序編號(時間戳),low(u)為u或u的子樹能夠追溯到的最早的棧中節點的次序號。由定義可以得出,

low(u)=min

當dfn(u)=low(u)時,以u為根的搜尋子樹上所有節點是乙個強連通分量。

演算法偽**如下

tarjan(u)

接下來是對演算法流程的演示。

下圖中,子圖為乙個強連通分量,因為頂點1,2,3,4兩兩可達。,也分別是兩個強連通分量。

從節點1開始dfs,把遍歷到的節點加入棧中。搜尋到節點u=6時,dfn[6]=low[6],找到了乙個強連通分量。退棧到u=v為止,為乙個強連通分量。

返回節點5,發現dfn[5]=low[5],退棧後為乙個強連通分量。

繼續回到節點1,最後訪問節點2。訪問邊(2,4),4還在棧中,所以low[2]=dfn[4]=5。返回1後,發現dfn[1]=low[1],把棧中節點全部取出,組成乙個連通分量。

至此,演算法結束。經過該演算法,求出了圖中全部的三個強連通分量,,。

可以發現,執行tarjan演算法的過程中,每個頂點都被訪問了一次,且只進出了一次堆疊,每條邊也只被訪問了一次,所以該演算法的時間複雜度為o(n+m)。

求有向圖的強連通分量還有乙個強有力的演算法,為kosaraju演算法。kosaraju是基於對有向圖及其逆圖兩次dfs的方法,其時間複雜度也是 o(n+m)。與trajan演算法相比,kosaraju演算法可能會稍微更直觀一些。但是tarjan只用對原圖進行一次dfs,不用建立逆圖,更簡潔。在實際的測試中,tarjan演算法的執行效率也比kosaraju演算法高30%左右。此外,該tarjan演算法與求無向圖的雙連通分量(割點、橋)的tarjan演算法也有著很深的聯絡。學習該tarjan演算法,也有助於深入理解求雙連通分量的tarjan演算法,兩者可以模擬、組合理解。

求有向圖的強連通分量的tarjan演算法是以其發明者robert tarjan命名的。robert tarjan還發明了求雙連通分量的tarjan演算法,以及求最近公共祖先的離線tarjan演算法,在此對tarjan表示崇高的敬意。

偽碼詳解:

tarjan(u)

我自己的**(棧用stl):

stacks;

int num = 0;

bool vis[maxn];

void tarjan(int r)

else if(vis[v[i]])

ll[r] = min(ll[r], ff[v[i]]); //如果在棧中找到s,說明棧內v到u一定是強連通分量,但要取最大的(棧最底下能滿足的)

} if(ff[r] == ll[r]) //如果是強連通的根,就輸出了

while(r != t); //一直迴圈下去

}}

呼叫:

for(int i = 1; i <= n; i++)

if(!ff[i])

tarjan(i);

縮點版(將強連通分量縮成乙個點):

stacks;

int p[maxn]; //p[i]表示第i個節點所在的強連通分量

int num = 0;

bool vis[maxn];

void tarjan(int r)

else if(vis[v[i]])

ll[r] = min(ll[r], ff[v[i]]); //如果在棧中找到s,說明棧內v到u一定是強連通分量,但要取最大的(棧最底下能滿足的)

} if(ff[r] == ll[r]) //如果是強連通的根,就輸出了

while(r != t); //一直迴圈下去

}}

呼叫是一樣的。建新圖的話,這樣:

這只是建邊而已,如果要權值什麼的,也可以在弄乙個鏈式前向星(自己習慣)。因為題目不需要,所以我只建邊。

m[maxn][maxn]; //表示新圖的邊

for(i = 1; i <= m; i++)

m[p[u[i]]][p[v[i]]] = 1;

回歸正題,本題的全部**:

#include #include #include using namespace std;

//如果某個愛心天使被其他所有人或愛心天使所愛則請輸出這個愛心天使是由哪些人構成的

//注意,還可以是某個愛心天使所愛(即找愛心天使是否為所有人的都可達,如果沒有就輸出-1,不是就不用輸出)

const int maxn = 1010, maxe = 10010;

int ans = 0, nn = 0;

int p[maxn], sum[maxn], ff[maxn], ll[maxn], ft = 0;

int head[maxn], u[maxn], v[maxe], next[maxe];

bool m[maxn][maxn];

int n, m;

stacks;

bool vis[maxn];

void tarjan(int r)

else if(vis[v[i]])

ll[r] = min(ll[r], ff[v[i]]);

} if(ff[r] == ll[r])

} }}

//縮點後再判斷是否為連通圖

int main()

for(i = 1; i <= n; i++)

if(!ff[i])

tarjan(i);

cout << ans << endl; //輸出天使數目

//建邊

for(i = 1; i <= m; i++)

m[p[u[i]]][p[v[i]]] = 1;

//floyd,判斷連通圖(資料小,能過)

for(k = 1; k <= nn; k++)

for(i = 1; i <= nn; i++)

for(j = 1; j <= nn; j++)

if(i!=j && m[i][k] && m[k][j]) //此處必須2條路都通,即i->k && k->j

m[i][j] = 1;

int ok = 1, no = 1;

for(i = 1; i <= nn; i++) }

if(no) cout << "-1\n";

return 0;

}

codevs 2822 愛在心中

題目描述 每個人都擁有乙個夢,即使彼此不相同,能夠與你分享,無論失敗成功都會感動。愛因為在心中,平凡而不平庸,世界就像迷宮,卻又讓我們此刻相逢our home。在愛的國度裡有n個人,在他們的心中都有著乙個愛的名單,上面記載著他所愛的人 不會出現自愛的情況 愛是具有傳遞性的,即如果a愛b,b愛c,則a...

Codevs 2822 愛在心中

2822 愛在心中 時間限制 1 s 空間限制 128000 kb 傳送門題目等級 鑽石 diamond 題目描述 description 每個人都擁有乙個夢,即使彼此不相同,能夠與你分享,無論失敗成功都會感動。愛因為在心中,平凡而不平庸,世界就像迷宮,卻又讓我們此刻相逢our home。在愛的國度...

codevs2822 愛在心中

題目描述 description 每個人都擁有乙個夢,即使彼此不相同,能夠與你分享,無論失敗成功都會感動。愛因為在心中,平凡而不平庸,世界就像迷宮,卻又讓我們此刻相逢our home。在愛的國度裡有n個人,在他們的心中都有著乙個愛的名單,上面記載著他所愛的人 不會出現自愛的情況 愛是具有傳遞性的,即...