C#中的裝箱和拆箱
無向圖求割邊
inline void Tarjan(int u,int fa){ dfn[u]=low[u]=++Time; for(int i=head[u];i!=-1;i=e[i].next){ int v=e[i].to; if(!dfn[v]){ Tarjan(v,u); low[u]=min(low[v],low[u]); if(low[v]>dfn[u]){ bridge[i]=bridge[i^1]=1;//邊的編號從零開始(顯然不滿足1^1=2,所以1的反邊應該是0) } } else if(v!=fa){// 無向圖中不存在重邊時,根據定義不能用dfn[fa]更新low[u] low[u]=min(low[u],dfn[v]); } } }
邊的編號從零開始(顯然不滿足1^1=2,所以1的反邊應該是0)
《進階指南上》邊編號從2開始(一上午都沒有看到,甚至一直在質疑作者的權威)
update2022 01 23
有向圖求強連通分量(縮點)
void Tarjan(int u){ dfn[u]=low[u]=++Time;vis[u]=1;s.push(u); for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(!dfn[v]){ Tarjan(v); low[u]=min(low[v],low[u]); } else if(vis[v])low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]){ tot++; while(s.top()!=u){ int v=s.top();s.pop(); vis[v]=0; belong[v]=tot; cnt[tot]++; } vis[u]=0;belong[u]=tot;cnt[tot]++;s.pop();//u出棧 } }
老姚的程式碼
Tarjan老姚
void tarjan(int u){ dfn[u] = low[u] = ++Time;//初始化 vis[u] = 1;//u為灰色,正在訪問中 s[++top]=u;//進棧 for (int i = heade[u]; i; i = e[i].next){ int v = e[i].to; if (!dfn[v]) {//v為白點 tarjan(v); low[u] = std::min(low[u], low[v]);//<u,v>為樹枝子節點low值更新父節點low值 }//否則有可能是返祖邊 else if (vis[v] == 1) low[u] = std::min(low[u], dfn[v]);//v 為灰色,表明 <u,>為返祖邊,有環 } vis[u] = -1;//u子樹訪問結束,變為黑點 if(dfn[u] == low[u]){//縮點 ++tot;//記錄新圖節點數 while(s[top+1]!=u){//從棧頂到u的點縮成一個新點tot int v=s[top]; belong[v]=tot;sum[tot]+=a[s[top--]]; }//belong表示強連通分量編號,vis表示是否在棧中,sum表示強連通分量權值和 } }
5_Lei看了老姚的部落格,學習了Tarjan,模板wa60。關於為什麼vis[u] = -1不可以while迴圈外面,我們進行了深入探討。感謝Chen_jr大佬的指點和hack
vis陣列用來標記是否在棧裡,從這個角度來看,u還沒有出棧如果沒有進入while迴圈就把vis清零顯然是不對的但是這個解釋我不接受
hack:有向環套有向環(1,2,3)(1,4,3,5)
根據定義,low值是指節點u能通過不在搜尋樹上的點訪問到的最早的點的時間戳,並且該點在棧中。由此不在棧中的點不能更新u,在棧中且u能訪問到的點一定要更新u。
如果一個u訪問到的點v不在棧裡,並且dfn[v]不為零,只有一種情況,v在一個環裡,並且已經執行了縮點的操作,如果用v的dfn更新u,會造成dfn[u]!=low[u]的情況,如果u是一個孤立點那麼新圖中就不會有這個點。
(忽略這個六)
我們用老姚的程式碼來模一下這個圖。
1 dfn[1]=1 ,low[1]=1 ,vis[1]=1
2 dfn[2]=2 ,low[2]=2 ,vis[2]=1
3 dfn[3]=3 ,low[3]=3 ,vis[3]=1
low[3]=min(dfn[1],low[3])=1
4 回溯 vis[3]=-1
5 回溯 low[2]=min(low[3],low[2])=1,
vis[2]=-1
6 dfn[4]=4 ,low[4]=4 ,vis[4]=1
7 dfn[5]=5 ,low[5]=5 ,vis[5]=1
8 vis[3]==-1不能更新
stack[5,4,3,2,1]
9 回溯vis[5]=-1
dfn[5]==low[5]=5,5出棧
10回溯vis[4]=-1
dfn[4]==low[4]=4,4出棧
11回溯vis[1]=-1
dfn[1]==low[1]=1, 3,2,1出棧
結束
由於5沒有被更新,所以5沒有被放到環裡,而是作為一個孤立點存到了新圖中,4同理。老姚vis更新早了,應該在出棧的過程中vis歸零
Tarjan求點雙&邊雙
在一個無向圖中,若任意兩點間至少存在兩條“點不重複”的路徑,則說這個圖是點雙連通的(簡稱雙連通,biconnected)
在一個無向圖中,點雙連通的極大子圖稱為點雙連通分量(簡稱雙連通分量,Biconnected Component,BCC)
inline void Tarjan(int u,int fa){//點雙
dfn[u]=low[u]=++Time;s.push(u);
int flag=0;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(!dfn[v]){
Tarjan(v,u);
if(low[v]>=dfn[u]){
flag++;
if(u!=root || flag>1)cut[u]=1;
tot++;int x=0;
do{
x=s.top();s.pop();
dcc[tot].push_back(x);
}while(x!=v);
dcc[tot].push_back(u);
}
low[u]=min(low[v],low[u]);
}
else if(v!=fa){//無向圖中不存在重邊時,根據定義不能用dfn[fa]更新low[u]
low[u]=min(low[u],dfn[v]);
}
}
}
Tarjan求邊雙
先跑一遍求出所有橋,dfs遇到橋就跳過
void dfs(int u){
belong_dcc[u]=dcc;
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].to;
if(bridge[i]==1||belong_dcc[v]!=0)continue;
dfs(v);
}
}
for(int i=1;i<=n;++i){
if(!belong_dcc[i]){
dcc++;dfs(i);
}
}