【基礎練習】【強連通tarjan】codevs1332 上白沢慧音題解

題目描述 Description

      在幻想鄉,上白澤慧音是以知識淵博聞名的老師。春雪異變導致人間之裏的很多道路都被大雪堵塞,使有的學生不能順利地到達慧音所在的村莊。因此慧音決定換一個能夠聚集最多人數的村莊作爲新的教學地點。人間之裏由N個村莊(編號爲1..N)和M條道路組成,道路分爲兩種一種爲單向通行的,一種爲雙向通行的,分別用1和2來標記。如果存在由村莊A到達村莊B的通路,那麼我們認爲可以從村莊A到達村莊B,記爲(A,B)。當(A,B)和(B,A)同時滿足時,我們認爲A,B是絕對連通的,記爲<A,B>。絕對連通區域是指一個村莊的集合,在這個集合中任意兩個村莊X,Y都滿足<X,Y>。現在你的任務是,找出最大的絕對連通區域,並將這個絕對連通區域的村莊按編號依次輸出。若存在兩個最大的,輸出字典序最小的,比如當存在1,3,4和2,5,6這兩個最大連通區域時,輸出的是1,3,4。 

輸入描述 Input Description

第1行:兩個正整數N,M

第2..M+1行:每行三個正整數a,b,t, t = 1表示存在從村莊a到b的單向道路,t = 2表示村莊a,b之間存在雙向通行的道路。保證每條道路只出現一次。

輸出描述 Output Description

第1行: 1個整數,表示最大的絕對連通區域包含的村莊個數。

第2行:若干個整數,依次輸出最大的絕對連通區域所包含的村莊編號。

樣例輸入 Sample Input

5 5

1 2 1

1 3 2

2 4 2

5 1 2

3 5 1

樣例輸出 Sample Output

3

1 3 5

數據範圍及提示 Data Size & Hint

對於60%的數據:N <= 200且M <= 10,000

對於100%的數據:N <= 5,000且M <= 50,000

基礎tarjan 先做一遍縮點 在每次出棧時統計每個強連通分量包含的點數以及最小的點的編號(因爲一個點只會出現在一個強連通分量=scc中,包含點數相同的情況下字典序最小的scc就是最小的點的編號最小的那個scc),最後把目標scc從vector轉移到數組中 排序並輸出

這次又犯了幾個低級錯誤 一個是把=達成了== 原來一直是把==打成= 這次竟然相反了= =

還有很重要的一個錯誤是,調試時發現tarjan找到小環就直接退棧了,並沒有找最大的環。和原來代碼對比才發現,當時的錯誤代碼是:

for (int i=head[k];i!=0;i=next[i])
    {
        if (!dfn[to[i]])//還沒有tarjan過 
        {
            tarjan(to[i]);
            low[k]=min(low[to[i]],low[k]);
        }
        else if (ins[to[i]])//如果tarjan過且在棧中說明是祖先,如果不在棧中說明是橫叉邊 
        {
            low[k]=min(low[to[i]],low[k]); 
        }
	if (dfn[k]==low[k])
        {
            tot++;
            int j,mi=0x3f3f3f;
            do
            {
                cnt[tot]++;
                j=sta[stp--];
                ins[j]=false;
                scc[tot].push_back(j);
                if (j<mi) mi=j;
            }
            while (j!=k);
            if (cnt[tot]>big) big=cnt[tot];
            first[tot]=mi;
        }
    }
    


而正解代碼應該是:

for (int i=head[k];i!=0;i=next[i])
    {
        if (!dfn[to[i]])//還沒有tarjan過 
        {
            tarjan(to[i]);
            low[k]=min(low[to[i]],low[k]);
        }
        else if (ins[to[i]])//如果tarjan過且在棧中說明是祖先,如果不在棧中說明是橫叉邊 
        {
            low[k]=min(low[to[i]],low[k]); 
        }
    }
    if (dfn[k]==low[k])
    {
        tot++;
        int j,mi=0x3f3f3f;
        do
        {
            cnt[tot]++;
            j=sta[stp--];
            ins[j]=false;
            scc[tot].push_back(j);
            if (j<mi) mi=j;
        }
        while (j!=k);
        if (cnt[tot]>big) big=cnt[tot];
        first[tot]=mi;
    }

發現有什麼不同了嗎?

對了,退棧過程的位置不一樣!這會帶來什麼呢?

原來,我們之所以要循環完畢再退棧而不是找到祖先就退棧,就是爲了防止找到小環直接退棧而忽略了大環存在的情況。

如果我們把退棧過程放在循環內部,那麼每當程序找到一個k的祖先x更新low[k]後,不滿足條件dfn[k]==low[k]就會直接退出子程序,退到上一層或幾層遞歸,到達祖先節點x那層時 祖先節點必然滿足dfn[k]==low[k],這時對於這個小環就會理所當然地出棧處理。

而我們應當注意的是,找到k的祖先x後,有可能k還有其他出邊,通向新的子節點,實際形成的強連通分量可能會更大,而實際的low[k]會更小。

如果我們把k的所有出邊循環一遍的話,當我們檢驗當前節點是否滿足low[k]==dfn[k]時,當前節點能找到的所有點都已經遍歷過了,因此如果先找到了祖先會暫時忽視,優先向深處查找更多的點。當滿足dfn[k]==low[k]時,必然有要麼搜到了葉子節點,要麼除了祖先其他節點都已訪問完畢,也就是該節點下面的子樹已經完全遍歷了,此時再更新low[k]時,得到的low[k]一定是最大環中當前節點所能追溯到的最早祖先(但注意同一強連通分量中low不一定相同,我們在上一篇搶掠計劃題解中已經證明),而當low[k]==dfn[k]時所有同一強連通分量中的點必然都在棧中,此時出棧得到的就是最大環的點了。


這個程序也再一次證明了

else if (ins[to[i]])//如果tarjan過且在棧中說明是祖先,如果不在棧中說明是橫叉邊 
        {
            low[k]=min(low[to[i]],low[k]); 
        }

在這裏,更新時寫成low[to[i]]或者dfn[to[i]]並沒有影響。


那麼這道題目的代碼如下:

//codevs1332 上白沢慧音 tarjan
//copyright by ametake
//task:求出每個scc並記錄每個scc包含的節點個數和編號 最後輸出最大的scc包含結點個數和編號 只輸出一個字典序最小的惡 
#include
#include
#include
#include
using namespace std;

const int maxn=5000+10;
const int maxm=50000+10;
int n,m;
int head[maxn],to[maxm*2],next[maxm*2],et=0;
int dfn[maxn],low[maxn],sta[maxn],dep=0,stp=0;
bool ins[maxn];
vector scc[maxn];
int cnt[maxn],tot=0;
int final[maxn],first[maxn]; 
int big=0,num=0;

inline void add(int &x,int &y)
{
    et++;
    to[et]=y;
    next[et]=head[x];
    head[x]=et;
}

inline int read()
{
    int f=1,a=0;
    char ch=getchar();
    while (ch<'0'||ch>'9')
    {
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9')
    {
        a=a*10+ch-'0';
        ch=getchar();
    }
    a*=f;
    return a;
}

void tarjan(int k)
{
    dfn[k]=low[k]=++dep;
    sta[++stp]=k;
    ins[k]=true;
    for (int i=head[k];i!=0;i=next[i])
    {
        if (!dfn[to[i]])//還沒有tarjan過 
        {
            tarjan(to[i]);
            low[k]=min(low[to[i]],low[k]);
        }
        else if (ins[to[i]])//如果tarjan過且在棧中說明是祖先,如果不在棧中說明是橫叉邊 
        {
            low[k]=min(low[to[i]],low[k]); 
        }
    }
    if (dfn[k]==low[k])
    {
        tot++;
        int j,mi=0x3f3f3f;
        do
        {
            cnt[tot]++;
            j=sta[stp--];
            ins[j]=false;
            scc[tot].push_back(j);
            if (jbig) big=cnt[tot];
        first[tot]=mi;
    }
}

void output()
{
    int mi=0x3f3f3f,best;
    for (int i=1;i<=tot;i++)
    {
        if (cnt[i]==big)
        {
            if (first[i]


——若爲化得身千億,散上峯頭望故鄉。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章