D16【模板】割点 Tarjan 算法
D16【模板】割点 Tarjan 算法_哔哩哔哩_bilibili
对于一个无向图,如果把一个点删除后这个图的极大连通分量数增加了,那么这个点就是这个图的割点(割顶)
对于一张连通的无向图,从任意一点 DFS,得到一棵 DFS 生成树,生成树上的边称作 树边,不在生成树上的边称作 非树边
由于 DFS 的性质,所有非树边连接的两个点在生成树上都满足其中一个是另一个的祖先

1. 对于某个顶点 x
,如果存在至少一个儿子 y,使得 $low[y]\ge dfn[x]$,即 y 不能回到 x 的祖先,那么 x
点为割点
例如 low[6]=dfn[5]=5,说明 5 为割点
2. 对于根节点的判断,需要存在至少两个儿子 y
,使得 $low[y]\ge dfn[x]$
根节点 1 有两个儿子 2 和 5 满足条件,故 1 是割点
注意:若 y 已访问,必须用 dfn[y] 更新 low[x]。否则,因为边是双向的,儿子会用父节点的 low 更新,最终每个点的 low =1,无法判割点
// Tarjan 算法 O(n+m) #include<bits/stdc++.h> using namespace std; const int N=20010; vector<int> e[N]; int n,m; int dfn[N],low[N],cut[N],root; void tarjan(int x){ dfn[x]=low[x]=++dfn[0]; int son=0; //x的儿子个数 for(int y:e[x]){ if(!dfn[y]){ //若y未访问 tarjan(y); low[x]=min(low[x],low[y]); if(low[y]>=dfn[x]){ //说明儿子y没有连通x的祖先 ++son; if(x!=root||son>1) cut[x]=1; //x不是根需要1个儿子,是根需要2个儿子 } } else //若y已访问 low[x]=min(low[x],dfn[y]); //注:dfn不能换成low } } int main(){ cin>>n>>m; for(int a,b;m--;)cin>>a>>b,e[a].push_back(b),e[b].push_back(a); for(root=1;root<=n;root++)if(!dfn[root]) tarjan(root); int ans=0; for(int i=1;i<=n;i++) if(cut[i]) ans++; cout<<ans<<"\n"; for(int i=1;i<=n;i++) if(cut[i]) cout<<i<<" "; }
P3469 [POI 2008] BLO-Blockade - 洛谷
#include<bits/stdc++.h> #define int long long using namespace std; const int N=100010; vector<int> e[N]; int n,m; int dfn[N],low[N],cut[N],siz[N],ans[N]; void tarjan(int x){ dfn[x]=low[x]=++dfn[0]; siz[x]=1; int son=0,sum=0; for(int y:e[x]){ if(!dfn[y]){ //若y未访问 tarjan(y); low[x]=min(low[x],low[y]); siz[x]+=siz[y]; if(low[y]>=dfn[x]){ // son++; // if(x!=1||son>1) cut[x]=1; ans[x]+=siz[y]*(n-siz[y]); //y子树的贡献 sum+=siz[y]; } } else low[x]=min(low[x],dfn[y]); } ans[x]+=(n-sum-1)*(sum+1); //x子树外的贡献 ans[x]+=n-1; //x的贡献 } signed main(){ ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); cin>>n>>m; for(int a,b;m--;)cin>>a>>b,e[a].push_back(b),e[b].push_back(a); tarjan(1); for(int i=1;i<=n;i++) cout<<ans[i]<<"\n"; }
浙公网安备 33010602011771号