1. 程式人生 > >淺談Tarjan縮點(分析+模板)

淺談Tarjan縮點(分析+模板)

鄰接表 ref 分享圖片 getchar() 是個 i++ iostream turn names

昨天一看發現我的博客數量到100篇了,撒花??ヽ(°▽°)ノ?


根據標題我們也知道,想要在接下來的十分鐘不浪費生命

讀者需要先行學習Tarjan強聯通分量

如果不會的話可以點擊這裏:https://www.cnblogs.com/WWHHTT/p/9744658.html

好的,現在我們就假設大家都知道了Tarjan,來看看如何利用Tarjan縮點

在學習如何寫代碼之前,我們必須要先弄明白什麽是縮點

也就是定義

百度百科上並沒有給出詳細解釋,我就來總結幾句

縮點就是把一個有向圖中的強聯通分量轉換成一個點

轉換完之後的圖是一個DAG(有向無環圖)

通常用來解決圖中具有傳遞性的問題(還有最大值和乘法原理)

我們來舉個例子吧

技術分享圖片

上面這個圖中,圓裏的是編號,紅色的是點的權值,箭頭代表邊的方向

這個圖縮點之後是這樣的

技術分享圖片

符號所表示的東西不變,原來的2,3,4變成了現在的2,原來的5是現在的3

是不是感覺沒有多難,下面我來帶大家看一下具體的實現流程

分為兩步:

1.求出圖中的強聯同分量,並轉化為點

2.將轉化出來的新的點建成一個新的圖

step1:

如何求出強聯通分量就不多說了(不會的去看上面的鏈接)

每一種強聯通分量會縮成一個點,這個店的編號我們就定為發現它的順序,也就是染上的顏色

每一種顏色(一個強聯通分量)對應一個點

但是要在中間加一個操作,用來計算一個聯通分量的所縮成的點的點權(看註釋)

void Tarjan(int x,int fa){
    low[x]=dfn[x]=++tot;
    sta[++size]=x;
    book[x]=1;
    for(int e=head1[x];e;e=nxt1[e]){
        if(!dfn[to1[e]]){
            Tarjan(to1[e],x);
            low[x]=min(low[x],low[to1[e]]);
        }
        else if(book[to1[e]]) low[x]=min(low[x],dfn[to1[e]]);
    }
    
if(dfn[x]==low[x]){ color[x]=++color_cnt; v2[color_cnt]=v[x]; while(size&&sta[size]!=x){ color[sta[--size]]=color_cnt; book[sta[size]]=0; v2[color_cnt]+=v[sta[size]];//這個點的權值的計算,根據題目來確定 } --size; } return ; }

step2:
建圖的邊怎麽練一直是個難點,首先可以確定的是因為原來的強連通分量已經變成一個個的點了

所以肯定無法照搬原圖

但也不可能自創,所以需要判斷,從原來的圖上選擇那些邊

選邊的原則是不能是一個強連通分量裏的邊

也就是說別的邊都可以選

那麽有的人就問了,縮完點之後不擔心友誼重邊嗎

這很好處理,用鄰接矩陣來存(不推薦),或者在之後的程序中標記都可以

特別提到,印的圖要用一個新的鄰接表來存

而且要連接的不是原來的點,而是兩個點被染成的顏色

代碼實現的話很簡單,循環和dfs都可以,這裏我們采用循環(代碼量較小)

void change(){
    for(int i=1;i<=n;i++){
        for(int e=head1[i];e;e=nxt1[e]){
            if(color[i]!=color[to1[e]]) add2(color[i],color[to1[e]]);
        }
    }
    return ;
}

那麽最後我們來總結一下代碼

先給一組數據:(對應上面的圖)
5 5 //點的數量 邊的數量

1 1 1 1 1 //點權

1 2 //邊

2 3

3 4

4 2

2 5

輸出:

3 //新的點的數量

1 3 1 //新的點的權值

下面給出代碼:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
inline int min(int a,int b){return a<b?a:b;}
inline int max(int a,int b){return a>b?a:b;}
inline int rd(){
    int x=0,f=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch==-) f=-1;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-0;
    return x*f;
}
inline void write(int x){
     if(x<0) putchar(-),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+0);
     return ;
}
int n,m;
int v[100006];
int head1[100006],nxt1[100006],to1[100006];
int total1=0;
void add1(int x,int y){
    total1++;
    to1[total1]=y;
    nxt1[total1]=head1[x];
    head1[x]=total1;
    return ;
}
int dfn[100006],low[100006];
int color[100006];
int tot=0;
int color_cnt=0;
int book[100006];
int sta[100006];
int v2[100006];
int size=0;
void Tarjan(int x,int fa){//Tarjan模板 
    low[x]=dfn[x]=++tot;
    sta[++size]=x;
    book[x]=1;
    for(int e=head1[x];e;e=nxt1[e]){
        if(!dfn[to1[e]]){
            Tarjan(to1[e],x);
            low[x]=min(low[x],low[to1[e]]);
        }
        else if(book[to1[e]]) low[x]=min(low[x],dfn[to1[e]]);
    }
    if(dfn[x]==low[x]){
        color[x]=++color_cnt;
        v2[color_cnt]=v[x];
        while(size&&sta[size]!=x){
            color[sta[--size]]=color_cnt;
            book[sta[size]]=0;
            v2[color_cnt]+=v[sta[size]];//這個點的權值的計算,根據題目來確定 
        }
        --size;
    }
    return ;
}
int head2[100006],nxt2[100006],to2[100006];
int total2=0;
void add2(int x,int y){
    total2++;
    to2[total2]=y;
    nxt2[total2]=head2[x];
    head2[x]=total2;
    return ;
}
void change(){
    for(int i=1;i<=n;i++){
        for(int e=head1[i];e;e=nxt1[e]){
            if(color[i]!=color[to1[e]]) add2(color[i],color[to1[e]]);//將兩個點連邊 
        }
    }
    return ;
}
int main(){
    n=rd(),m=rd();
    for(int i=1;i<=n;i++) v[i]=rd();//得到點權 
    for(int i=1;i<=m;i++){
        int x=rd(),y=rd();
        add1(x,y);//有向邊 
    }
    Tarjan(1,0);
    change();
    printf("%d\n",color_cnt);
    for(int i=1;i<=color_cnt;i++){
        printf("%d ",v2[i]);
    }
    return 0;
}

淺談Tarjan縮點(分析+模板)