強連通分量與縮點(Tarjan演算法)(洛谷P3387)
名詞解釋:
強連通分量:
有向圖強連通分量:在有向圖G中,如果兩個頂點vi,vj間(vi>vj)有一條從vi到vj的有向路徑,同時還有一條從vj到vi的有向路徑,則稱兩個頂點強連通(strongly connected)。如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。有向圖的極大強連通子圖,稱為強連通分量(strongly connected components)。
簡單點說,即在有向圖中對於某個子圖S,如果S中的點兩兩都直接或間接的相連,則稱這個子圖為強連通分量。(要求最大的滿足條件的子圖)
縮點:
把一個強連通分量用一個點代替的操作稱為縮點。
演算法實現
對於縮點,我們採用Tarjan演算法來實現它。
Tarjan其實就是一個DFS的過程。
所需記錄的量
開一個棧來記錄dfs的軌跡。
對於節點i,記錄dfn[i]表示i是第幾個被訪問的,low[i]表示以i為父親的節點能訪問到的在棧中最前面的節點。(即i指向的節點的最小非零dfn值,因為會有環存在,所以上述情況是有可能發生的)。
遍歷過程
程式碼解釋:
void tarjan(int x){//當前遍歷到x
dfn[x]=low[x]=++p;//初始化dfn與low
stack[++top]=x;//入棧
f[x]=true;//標記入棧
for (int i=h1[x];i;i=ed[i].next)//訪問其兒子
if (!dfn[ed[i].to]){//如果還沒有做過
tarjan(ed[i].to);//繼續遍歷
low[x]=min(low[x],low[ed[i].to]);//遞迴完成後更新low值
}
else //如果已經做過
if (f[ed[i].to])//如果在棧內
low[x]=min(low[x],dfn[ed[i].to]);//更新low值
if (low[x]==dfn[x]){//如果到底
f[x]=false;//判其出棧
k++;
while (stack[top+1]!=x){//棧要彈到當前節點位置
//在這裡可以更新一些其他的東西
num[stack[top]]=k;//給新的強連通分量標號
f[stack[top--]]=false;//出棧
}
num[x]=k;//當前節點也屬於新的強連通分量
}
}
模板
縮點配合最短(長)路演算法可以用於求有向圖中路徑上的最大點權和。
以洛谷P3387為例:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 10000
using namespace std;
struct edge{
int next;
int to;
};
int n,m,k,p,top,ans;
int dfn[MAXN+5],low[MAXN+5],stack[MAXN+5],num[MAXN+5];
int dis[MAXN+5],que[MAXN*10+5],h1[MAXN+5],h2[MAXN+5],w[MAXN+5];
edge ed[MAXN*20+5];
bool f[MAXN+5];
inline char readc(){//讀優
static char buf[100000],*l=buf,*r=buf;
if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
if (l==r) return EOF; return *l++;
}
inline int _read(){
int num=0; char ch=readc();
while (ch<'0'||ch>'9') ch=readc();
while (ch>='0'&&ch<='9') { num=num*10+ch-48; ch=readc(); }
return num;
}
void addedge(int x,int y,int *h){
ed[++k].next=h[x];
ed[k].to=y;
h[x]=k;
}
void tarjan(int x){//縮點
dfn[x]=low[x]=++p;
stack[++top]=x;
f[x]=true;
for (int i=h1[x];i;i=ed[i].next)
if (!dfn[ed[i].to]){
tarjan(ed[i].to);
low[x]=min(low[x],low[ed[i].to]);
}
else
if (f[ed[i].to]) low[x]=min(low[x],dfn[ed[i].to]);
if (low[x]==dfn[x]){
f[x]=false;
while (stack[top+1]!=x){
num[stack[top]]=x;//這裡直接把所有點都縮到祖先上
f[stack[top--]]=false;
}
num[x]=x;
}
}
void spfa(int now){//求最大點權和
int l=0,r=1; que[1]=now; dis[now]=w[now]; ans=max(ans,dis[now]);
while (l<r){
int x=que[++l];
f[x]=false;
for (int i=h2[x];i;i=ed[i].next)
if (dis[ed[i].to]<dis[x]+w[ed[i].to]){
dis[ed[i].to]=dis[x]+w[ed[i].to];
ans=max(ans,dis[ed[i].to]);
if (!f[ed[i].to]){
f[ed[i].to]=true; que[++r]=ed[i].to;
}
}
}
}
int main(){
n=_read(); m=_read();
for (int i=1;i<=n;i++)
w[i]=_read();
for (int i=1;i<=m;i++){
int u=_read(),v=_read();
addedge(u,v,h1);
}
for (int i=1;i<=n;i++)//要保證每個點都被縮過
if (!dfn[i])
tarjan(i);
for (int i=1;i<=n;i++)
if (num[i]!=i)
w[num[i]]+=w[i];
for (int i=1;i<=n;i++)
for (int j=h1[i];j;j=ed[j].next)
if (num[i]!=num[ed[j].to])//如果他們不屬於一個強連通分量
addedge(num[i],num[ed[j].to],h2);//重新建邊
for (int i=1;i<=n;i++)
if (num[i]==i&&!dis[i])
spfa(i);
printf("%d\n",ans);
return 0;
}
相關文章
- 【模板】tarjan 強連通分量縮點
- 強連通分量及縮點tarjan演算法解析演算法
- 圖之強連通、強連通圖、強連通分量 Tarjan演算法演算法
- POJ 2186 Popular Cows(強連通分量縮點,Tarjan演算法)演算法
- 強連通分量(Tarjan演算法)演算法
- 圖論複習之強連通分量以及縮點—Tarjan演算法圖論演算法
- Tarjan演算法(強連通分量分解)演算法
- 圖論——強連通分量(Tarjan演算法)圖論演算法
- 強連通------tarjan演算法詳解及與縮點聯合運用演算法
- Tarjan演算法求強連通分量總結演算法
- 強連通分量-tarjan演算法模板詳解演算法
- HDU 2767 Proving Equivalences Tarjan 強連通縮點UI
- 演算法學習之路|強連通分量+縮點演算法
- kosaraju 和 tarjan演算法詳解(強連通分量)演算法
- Tarjan演算法三大應用之強連通分量演算法
- 【演算法學習】tarjan 強連通、點雙、邊雙及其縮點 重磅來襲!!!!演算法
- 【Tarjan 拓撲排序 dp】P3387 【模板】縮點排序
- 強連通分量及縮點 演算法解析及例題演算法
- Tarjan 求有向圖的強連通分量
- Trajan演算法(強連通+縮點)演算法
- 尋找圖的強連通分量:tarjan演算法簡單理解演算法
- 連通圖與Tarjan演算法演算法
- 20行程式碼實現,使用Tarjan演算法求解強連通分量行程演算法
- Tarjan演算法_縮點演算法
- HDU2767Proving Equivalences[強連通分量 縮點]UI
- 強連通演算法--Tarjan個人理解+詳解演算法
- tarjan演算法求scc & 縮點演算法
- 強連通分量
- 【筆記】tarjian演算法 求強連通分量筆記演算法
- dfs與貪心演算法——洛谷5194演算法
- 連通圖演算法詳解之① :Tarjan 和 Kosaraju 演算法演算法
- [題解] [洛谷P7883] 平面最近點對(加強版)
- 洛谷
- Tarjan縮點題單 刷題題解
- 強連通圖的演算法演算法
- tarjan縮點-受歡迎的牛-筆記筆記
- 洛谷團隊
- 洛谷P1972(莫隊演算法)演算法