淺談Tarjan縮點(分析+模板)
昨天一看發現我的博客數量到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縮點(分析+模板)