縮點(有向圖的強連通份量)學習筆記

縮點(有向圖的強連通份量)學習筆記

1.什麼是強連通份量?:html

有向圖強連通份量:在有向圖G中,若是兩個頂點vi,vj間(vi>vj)有一條從vi到vj的有向路徑,同時還有一條從vj到vi的有向路徑,則稱兩個頂點強連通(strongly connected)。若是有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。有向圖的極大強連通子圖,稱爲強連通份量(strongly connected components)。——度娘c++

顯然正確,可是文縐縐的又難懂算法

其實就是有一大團點,它們之間能夠互相到達,且沒有其它的點與它們互可到達,那麼這一大團點就是有向圖的一個強聯通份量。數組

一個強連通份量能夠看做一個或幾個環拼接在一塊兒。學習

如:spa

2.如何操做?:code

首先對於整個有向圖進行dfs,若dfs樹中存在子樹有到達父親節點,從父親到這個點則均可互相到達,這些點有可能與父親原屬的強連通圖合併,也有可能成爲新的一個強連通份量。component

如上圖。htm

因此咱們判斷一個點是否在一個強連通份量中,就用它是否能到達dfs序比他小的點。blog

但咱們如何將一個強連通份量的點記錄呢?

答:用一個棧儲存,遇到一個不能到達dfs序比他小的點的點,就將棧裏的點都標記爲一個新的強連通份量,再清空棧(一個點咱們也看做一個強連通份量)。

咱們用dfn數組記錄dfs序,low數組記錄能到達的點最小的dfs編號。

對於每個點,須要用它的兒子的low來更新本身的low,如上圖中的2號點;

然而,如有連向父親的邊呢?是否是直接用父親的dfn來更新本身的low?

固然是對的!!!(做者就曾經錯在這裏,曾今覺得是對的,但打模板時錯了,改了兩處,結果覺得是由於將dfn改成low纔對的,後面發現不是,果真我仍是太蒟蒻了qaq~~~~~

在tarjan算法中,都是用父親的dfn來更新本身的low,

但在有向圖中,若本身的父親有連向祖父等祖宗,那麼本身和祖宗在同一個強連通份量;

而在無向圖中,則屬於兩個強連通份量。

如:

                                 

其中,一、二、三、四、5互可到達,只有dfn[1]==low[1],都是一個強連通份量。

           

此時,用low來更新錯誤

 

還有,注意:對於已經屬於一個強連通份量的點,不能用它的low來更新連向它的點

       

因此,low[5]爲5。

 

 1 void tarjan(int u)//當前點
 2 {  3     low[u]=dfn[u]=++deep; x[++top]=u; v[u]=1;  4     for(int i=head1[u];i;i=e[i].nxt)  5  {  6         if(!dfn[e[i].to]) tarjan(e[i].to),low[u]=min(low[u],low[e[i].to]);//連向兒子的邊
 7         else if(v[e[i].to]) low[u]=min(low[u],low[e[i].to]);//連向父親且不屬於一個強連通份量
 8  }  9    if(dfn[u]==low[u])//找到一個強連通份量
10  { 11         num[u]=++tot; v[u]=0; 12         while(x[top]!=u) v[x[top]]=0,num[x[top]]=tot,--top; 13         --top; 14  } 15 } 16 int main() 17 { 18     for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i); 19     return 0; 20 }

 

 

 

最後,模擬一個圖的tarjan過程:

 

一共有一個強連通份量:1.{1,2,3,4,5,6,7,8,9}。

u=1:dfn[1]=1,++top,x[1]=1,tarjan(8);

u=8:dfn[8]=2,++top,x[2]=8,tarjan(9);

u=9:dfn[9]=3,++top,x[3]=9,low[9]=1;

u=8:low[8]=1;

u=1:tarjan(2);

u=2:dfn[2]=4,++top,x[4]=2,tarjan(3);

u=3:dfn[3]=5,++top,x[5]=3,tarjan(4);

u=4:dfn[4]=6,++top,x[6]=4,low[4]=low[2]=4;

u=3:low[3]=low[4]=4;

u=2:low[2]=4,tarjan(5);

u=5:dfn[5]=7,++top,x[7]=5,tarjan(6);

u=6:dfn[6]=8,++top,x[8]=6,tarjan(7);

u=7:dfn[7]=9,++top,x[9]=7,low[7]=1;

u=6:low[6]=1;

u=5:low[5]=1;

u=2:low[2]=1;

u=1:dfn[1]==low[1]=1→++tot,將x[1]到x[9]標記爲tot=1,top=0;

結束。

3.縮點:

將每一個強聯通份量看做一點,將全部連向強聯通份量內的點連向這個強聯通份量。

 1 int main()  2 {  3     n=read(); m=read();  4     for(int i=1;i<=n;++i) w1[i]=read();  5     for(re int i=1;i<=m;++i) t1=read(),t2=read(),add(t1,t2,e,head1);  6     cnt=0;  7     for(int i=1;i<=n;++i)  8  {  9         w2[num[i]]+=w1[i]; 10         for(int j=head1[i];j;j=e[j].nxt) if(num[i]!=num[e[j].to]) add(num[i],num[e[j].to],f,head2),++rd[num[e[j].to]]; 11  } 12     return 0; 13 }

洛谷P3387 【模板】縮點

題目背景

縮點+DP

題目描述

給定一個n個點m條邊有向圖,每一個點有一個權值,求一條路徑,使路徑通過的點權值之和最大。你只須要求出這個權值和。

容許屢次通過一條邊或者一個點,可是,重複通過的點,權值只計算一次。

輸入輸出格式

輸入格式:

 

第一行,n,m

第二行,n個整數,依次表明點權

第三至m+2行,每行兩個整數u,v,表示u->v有一條有向邊

 

輸出格式:

 

共一行,最大的點權之和。

 

輸入輸出樣例

輸入樣例#1: 
2 2
1 1
1 2
2 1
輸出樣例#1: 
2

說明

n<=10^4,m<=10^5,0<=點權<=1000

算法:Tarjan縮點+DAGdp

題解:

當進入一個強連通份量時,確定將這個強連通份量都走一遍,因此縮點,變爲一個DAG(有向無環圖),而後按拓撲序dp,每一個點的答案爲全部能到達它的點的答案的最大值+本身的點值,輸出全部點中答案的最大值。

用每一個點的答案更新它所能到達的全部點的答案。

代碼以下:

 1 #include<bits/stdc++.h>
 2 #define re register
 3 using namespace std;  4 const int N=10006,M=100006;  5 int n,m,t1,t2,w1[N],w2[N],num[N],cnt=0,head1[N],t,tot=0,head2[N],x[N],dfn[N],low[N],ans[N],maxa=-1,rd[N],deep=0,top=0,v[N];  6 struct edge  7 {  8     int to,nxt;  9 }e[M],f[M]; 10 inline void add(int u,int v,edge a[],int head[]){a[++cnt].to=v,a[cnt].nxt=head[u],head[u]=cnt;} 11 inline int read() 12 { 13     int T=0,F=1; char ch=getchar(); 14     while(ch<'0'||ch>'9'){if(ch=='-') F=-1; ch=getchar();} 15     while(ch>='0'&&ch<='9') T=(T<<3)+(T<<1)+(ch-48),ch=getchar(); 16     return F*T; 17 } 18 void tarjan(int u) 19 { 20     low[u]=dfn[u]=++deep; x[++top]=u; v[u]=1; 21     for(int i=head1[u];i;i=e[i].nxt) 22  { 23         if(!dfn[e[i].to]) tarjan(e[i].to),low[u]=min(low[u],low[e[i].to]); 24         else if(v[e[i].to]) low[u]=min(low[u],low[e[i].to]); 25  } 26     if(dfn[u]==low[u]) 27  { 28         num[u]=++tot; v[u]=0; 29         while(x[top]!=u) v[x[top]]=0,num[x[top]]=tot,--top; 30         --top; 31  } 32 } 33 void tppx() 34 { 35     queue<int> q; cnt=0; 36     for(int i=1;i<=tot;++i) if(!rd[i]) x[++cnt]=i,q.push(i); 37     while(!q.empty()) 38  { 39         int tmp=q.front(); q.pop(); 40         for(int i=head2[tmp];i;i=f[i].nxt) 41  { 42             --rd[f[i].to]; 43             if(!rd[f[i].to]) x[++cnt]=f[i].to,q.push(f[i].to); 44  } 45  } 46 } 47 int main() 48 { 49     n=read(); m=read(); 50     for(int i=1;i<=n;++i) w1[i]=read(); 51     for(re int i=1;i<=m;++i) t1=read(),t2=read(),add(t1,t2,e,head1); 52     for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i); 53     cnt=0; 54     for(int i=1;i<=n;++i) 55  { 56         w2[num[i]]+=w1[i]; 57         for(int j=head1[i];j;j=e[j].nxt) if(num[i]!=num[e[j].to]) add(num[i],num[e[j].to],f,head2),++rd[num[e[j].to]]; 58  } 59     memset(x,0,sizeof(x)); tppx(); 60     for(int i=1;i<=n;++i) 61  { 62         t=x[i]; ans[t]=max(ans[t],w2[t]); 63         maxa=max(maxa,ans[t]); 64         for(int j=head2[t];j;j=f[j].nxt) ans[f[j].to]=max(ans[f[j].to],ans[t]+w2[f[j].to]); 65  } 66     printf("%d\n",maxa); 67     return 0; 68 }
相關文章
相關標籤/搜索