$ \ \ \ \ \ \ $照個人理解,拓撲排序通常是在dag圖中尋找乙個適合的解決問題的順序。
方法1:bfs(spfa優化)
1、先尋找入度為0的點,把它加入佇列。
2、搜尋佇列,把佇列的點g刪去,則如果有點的入度有g點的話,入度- -,當發現又出現入度為0的點時,將該點加入佇列。
3、拓撲排序的結果為該佇列,在執行刪點操作的時候儲存在乙個陣列及可。
方法2:記憶化搜尋
大多數情況下,並不需要顯式的拓撲排序
考慮樸素的回溯演算法
若從乙個給定的點出發,得到的結果是一樣的
因此對於每個點,計算完成後可以把結果儲存起來,之後直接返回查表的結果即可
拓撲排序偽**(1):
topological_sort(g)
return result
}
拓撲排序偽**(2):
calculate(u)
for (i 是g的所有節點)
result = max(result, calculate(i))
print(result)
ps:原始碼在我講完縮點後一起放出來$ \ \ \ \ \ \ $現在給出乙個有向有環圖,那麼這個圖不是乙個dag,所以不能在這種圖上做拓撲排序或其他有關dag的操作了。
如果我們單獨把1,2,3點提出來,把它們看做乙個團。
我們把這樣乙個「團點」叫做強連通分量(scc, strong connected component)
通常來講,一組互相能到達的點叫做連通分量
當這個連通分量不能再大時,便是強連通分量
把有向有環圖抽象成一顆dfs樹。
那麼每乙個圖上的圈圈就是乙個強連通分量。在dfs樹中,強連通分量一定長成這樣子。
那麼問題就被化簡成了確定每個強連通分量的根。
dfs時我們維護兩個陣列dfn,low
dfn[i]是i點的進入時間
low[i]是從i點出發,所能訪問到的最早的進入時間
tarjan-scc偽碼
dfs(u)
dfn[u] = low[u] = ++timer
stack.push(u)
state[u]=1 //已訪問併入棧
for v 是u的一條出邊的端點
if (state[v] == 0) //未訪問
dfs(v)
low[u] = min(low[u], low[v])
if (state[v] == 1)
low[u] = min(low[u], dfn[v])
if (dfn[u] == low[u])
stack.pop() until 彈出了u //這些點構成乙個強連通分量
彈出的點的state = 2
tarjan_scc(g)
timer = 0
for u 是圖g的節點
if (state[u] == 0) dfs(u)
那怎麼找出乙個 強連通分量的所有點
找出scc之後,問題通常會變成兩個部分
1、scc內部
2、scc之間,把每個scc看成乙個點,則是dag圖
新圖怎麼連邊?
記belong[u]為u所在的scc編號
對於每條邊u -> v
若belong[u] != belong[v],則給新圖加邊 belong[u] -> belong[v]
洛谷【p3387 縮點】
縮點+拓撲排序+dp
**:
#include#define maxn 100001
#define maxm 500001
using namespace std;
struct nodeedge[maxm];
queue q;
vector cb[maxn];
vector rdr[maxn];
int ans[maxn],totq,x,y,v,rd[maxn],u,n,m,sum,vis[maxn],dis_[maxn],dis[maxn];
int dfn[maxn],low[maxn],f[maxn],times,cntqq;
int stack_[maxn],heads[maxm],visit[maxn],cnt,tot,index_;
void add(int x,int y) //建邊
void tuopu() //拓撲排序
while(!q.empty())
}}void tarjan(int x) //tarjan求強連通分量
else
if(visit[edge[i].to])
low[x]=min(low[x],dfn[edge[i].to]);
}if(low[x]==dfn[x])
}}int main()
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i); //tarjan
for(int i=1;i<=cntqq;i++)
}tuopu();
for(int i=1;i<=tot;i++) //dp
for(int i=1;i<=tot;i++) //最後統計答案
sum=max(f[i],sum);
printf("%d",sum);
return 0;
}//剛剛好100行
$ \ \ \ \ \ \ $現在問題又進一步公升級了,有向圖變成了無向圖,那麼完全不可能成為乙個dag了,所以,我們上面討論的強連通分量等神奇東西在無向圖是沒有意義的,那麼無向圖有什麼操作呢?
$ \ \ \ \ \ \ $無向圖一般討論橋,割點,點雙連通分量或邊雙連通分量
1、若刪除一條邊後該圖不連通,則該邊為橋
2、若刪除乙個點後該圖不連通,則該點為割點
3、無割點的圖稱是點雙連通的,極大的點雙連通子圖稱為點雙連通分量
4、無橋的圖稱是邊雙連通的,極大的邊雙連通子圖稱為邊雙連通分量
核心仍然是求dfn和low
主體與有向圖類似,有兩點注意
節點只有兩種狀態:是否搜尋過
要特判是否父親搜尋過來的邊
無向圖tarjan偽碼
dfs(u)
dfn[u] = low[u] = ++timer
vis[u] = true
for v 是u的一條出邊(非父邊)的端點
if (!vis[v]) //未訪問
dfs(v)
low[u] = min(low[u], low[v])
else
low[u] = min(low[u], dfn[v])
tarjan(g)
timer = 0
for u 是圖g的節點
if (!vis[u]) dfs(u)
判斷割點和橋
割點
存在兒子v,low[v] >= dfn[u],則u是割點
根節點特判:若有兩個或以上的兒子,則是割點
橋
回邊不是橋
對於樹邊,父親記為u,兒子記為v,若low[v] > dfn[u],則該邊是橋
洛谷【p3388 割點】
割點**:
#includeusing namespace std;
struct edgee[200010];
int n,m,times,cnt,tot,a,b;
int head[100010],dfn[100010],low[100010];
bool vis[100010];
void add(int x,int y)
void tarjan(int u,int father)
low[u]=min (low[u],dfn[v]);
}if(son>=2&&u==father)
vis[u]=1;
}int main()
for(int i=1;i<=n;i++)
if(dfn[i]==0)
tarjan(i,i);
for(int i=1;i<=n;i++)
if(vis[i])
tot++;
printf("%d\n",tot);
for(int i=1;i<=n;i++)
if(vis[i])
printf("%d ",i);
return 0;
}
我將這兩個問題留給大家,如果實在有需要的可以私信我。
question1:如何求點(邊)雙連通分量
question2:如何記點(邊)雙連通分量
p2341 [haoi2006]受歡迎的牛
p2002 訊息擴散
p1262 間諜網路
poj 1236
poj 2186
poj 2762
poj 3687
強連通分量 tarjan求強連通分量
雙dfs方法就是正dfs掃一遍,然後將邊反向dfs掃一遍。挑戰程式設計 上有說明。雙dfs 1 include 2 include 3 include 4 include 5 6using namespace std 7const int maxn 1e4 5 8 vector g maxn 圖的鄰...
強連通分量
對於有向圖的乙個頂點集,如果從這個頂點集的任何一點出發都可以到達該頂點集的其餘各個頂點,那麼該頂點集稱為該有向圖的乙個強連通分量。有向連通圖的全部頂點組成乙個強連通分量。我們可以利用tarjan演算法求強連通分量。define n 1000 struct edge e 100000 int ec,p...
強連通分量
在有向圖g中,如果兩個頂點間至少存在一條路徑,稱兩個頂點強連通 strongly connected 如果有向圖g的每兩個頂點都強連通,稱g是乙個強連通圖。非強連通圖有向圖的極大強連通子圖,稱為強連通分量 strongly connected components 下圖中,子圖為乙個強連通分量,因為...