1. 程式人生 > 其它 >C#中的裝箱和拆箱

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);
        }
    }