牛客D-Where are you /// kruskal+tarjan找無向圖內的環
題目大意:
https://ac.nowcoder.com/acm/contest/272/D
在一個無向圖中,給定一個起點,從起點開始走遍圖中所有點
每條邊有邊權wi,表示第一次經過該道路時的花費(第二次及以後經過時花費為0)
此時用最少花費完成可能存在多種方案
求每種方案都必須經過的邊有多少條
首先想到最小生成樹
然後想到在得到最短邊時 若存在其他長度相等的邊 這條邊此時就可被替代
但如果沒有長度相等的邊 那麼這條邊就是必須經過的邊
然而這個想法經不起考驗 是錯誤的 如下
但是沒有長度相等的邊 就是必須經過的邊 這是毋庸置疑的
那麼再觀察下圖
可以看到這是 一個只由長度相等的邊組成的圖
若在這個圖中 我們要得到一棵生成樹的話 邊3-4是必選的 而剩下的1-2、2-3、3-1則任選兩條即可
也就是說在生成樹的所有選擇方案裡 這三條邊不是必須經過的邊
那麼可以發現 在一個只由長度相等的邊組成的圖內 能形成一個環的幾條邊不是在所有的選擇方案裡必須經過的邊
再結合kruskal得到最小生成樹的步驟 每次先得到所有相同長度的最短邊建圖
再找到其中的環的個數m 那麼要連線所有的環 必須經過的邊就有m-1條
tarjan 求無向圖內的環 就是在 有向圖求強聯通分量 的基礎上進行修改
將 已走過的邊 視為有向 不走其反向邊
那麼當走完這個圖之後 整個圖變成了一個有向圖 此時圖中的強聯通分量就是環
如何 將已走過的邊視為有向 呢
首先建圖的過程中 對於一條邊 我們是連了正向就連反向的 也就是這兩條有向邊在儲存過程中的序號是連續的
所以我們從序號2開始存邊的話 序號為 2和3 的兩條有向邊對應一條無向邊 4和5對應一條無向邊 6和7對應一條......
則對於 儲存順序為第 x 的有向邊 其對應的反向邊(即另一條有向邊)順序為 (x^1)
那麼我們在遞迴時將上一條邊的順序 last 作為引數傳過來 不走它對應的反向邊即跳過順序為 last^1 的邊 就可以了
#include <bits/stdc++.h> #define mem(i,j) memset(i,j,sizeof(i)) using namespace std; const int N=2e5+5; struct EDGE { int u,v,w; bool operator <(const EDGE& p)const { return w<p.w; } }E[N<<1]; struct NODE { int to,nt; }e[N<<1]; int head[N], tot; void addE(int u,int v) { e[++tot].to=v; e[tot].nt=head[u]; head[u]=tot; } int dfn[N], low[N], ind; int fa[N], ans; int n, m, p; void init_e() { ind=0; mem(dfn,0); tot=1; mem(head,0); } void init_s() { ans=0; for(int i=1;i<=n;i++) fa[i]=i; } int tarjan(int u,int last) { dfn[u]=low[u]=++ind; int res=0; for(int i=head[u];i;i=e[i].nt) { if(i==(last^1)) continue; // 是上一條邊的對應反向邊 跳過 int v=e[i].to; if(!dfn[v]) { res+=tarjan(v,i); low[u]=min(low[u],low[v]); } else low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]) res++; return res; } int getfa(int x) { if(x==fa[x]) return x; return fa[x]=getfa(fa[x]); } void unite(int x,int y) { x=getfa(x), y=getfa(y); if(x!=y) fa[x]=y; } void Kruskal() { sort(E,E+m); for(int i=0,j;i<m;i=j) { j=i; while(j<m && E[j].w==E[i].w) j++; //找到所有與最短邊相等的邊 init_e(); // 初始化鄰接表和tarjan需要的陣列 for(int k=i;k<j;k++) { // 建圖 int u=getfa(E[k].u), v=getfa(E[k].v); if(u==v) continue; // 兩個點已經連起來了 addE(u,v); addE(v,u); } for(int k=i;k<j;k++) { int u=getfa(E[k].u); if(!dfn[u]) ans+=tarjan(u,0)-1; //保證m個環連通 需要m-1條邊 } for(int k=i;k<j;k++) unite(E[k].u,E[k].v); } } int main() { while(~scanf("%d%d%d",&n,&m,&p)) { init_s(); for(int i=0;i<m;i++) scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w); Kruskal(); printf("%d\n",ans); } return 0; }View Code