tarjan无向图割点与割边板子

\(\Huge{割点}\)

\(无向图割点判定法则\)

当遍历到一个点x时,这个点为割点的情况有两种:

  • 第一种是该节点为根节点且子节点数>=2,必导致两个子节点不连通;
  • 第二种是该节点不为根节点,且 \(low[i]>=dfn[x]\),即子节点i可回溯到的最早的点不早于x点,那么删去x一定导致x的子节点不连通;

反之,若 \(low[i]<dfn[x]\),则说明i能绕行到比x更早的点,则x不是割点;(即环内的点割不掉);
image

//无向图割点模板
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define N 20001
using namespace std;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool f=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') f=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(f?x:~x+1);
}
int n,m,a,b,tot,root,ans,dfn[N],low[N];
bool cut[N];
vector<int> e[N];
void tarjan(int x)
{
	dfn[x]=low[x]=++tot;
	int child=0;
	for(int y:e[x])
		if(!dfn[y])
		{
			tarjan(y),
			low[x]=min(low[x],low[y]);
			if(low[y]>=dfn[x])
			{
				child++;
				if(x!=root||child>1) cut[x]=1;
			}
		} 
		else low[x]=min(low[x],dfn[y]);
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
	read(n),read(m);
	while(m--) 
		read(a),read(b),
		e[a].push_back(b),
		e[b].push_back(a);
	for(root=1;root<=n;root++)
		if(!dfn[root]) tarjan(root);
	for(int i=1;i<=n;i++) if(cut[i]) ans++;
	cout<<ans<<endl;
	for(int i=1;i<=n;i++) if(cut[i]) cout<<i<<' ';
}
  • 样例输入:
    7 10
    1 2
    2 3
    3 4
    2 4
    1 4
    1 5
    5 6
    5 8
    7 8
    5 7
  • 样例输出
    2
    1 5
    image

\(\Huge{割边}\)

无向图的割边判定法则

  • 在遍历到一个点x 的子节点y,满足 \(low[y]>dfn[x]\),则 \((x,y)\) 这条边就是割边;
  • \(low[y]>dfn[x]\) ,说明不经过 \((x,y)\) ,y无法到达比x更早的点,故割掉这条边;
  • 反之,若 \(low[y]<=dfn[x]\),则y能够到达x或比x更早的点,\((x,y)\) 就不是割边,即环内没有割边;

\(注意事项:\)

  • 求桥的时候,议案为边是无向的,所以父亲与孩子的关系也要自己规定一下,正常的 $ else low[x]=min(low[x],low[y])$ 应改为 $ else if(y!=fa) low[x]=min(low[x],low[y])$;因为如果y是x的父亲,这条无向边就被误认为是环了;

  • 找桥的时候,有重边的一定不是桥;
    image
    image
    割边是(1,2)和(1,5)。

板子:洛谷 P1656 炸铁路

  • 题目描述
    A国要使B国的铁路系统瘫痪,要炸毁一条铁路使其铁路无法全部联通,问可炸毁那一条铁路?
  • 输入格式
    第一行 \(n,m\) (\(1<=n<=150,1<=m<=5000\)),分别表示n个城市和m条铁路。
    以下m行,每行 \(a,b\) ,表示城市a和城市b间有铁路直接联通。
  • 输出格式
    输出若干行,每行 \(a,b\) ,其中 \(a<b\) ,表示\((a,b)\)\(key road\) .
    请注意:输出时,所有的数对必须按照a从小到大排序输出,若a相同,根据b从小到大输出。

代码实现:

//无向图割边模板,炸铁路
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define N 200
#define M 20001
using namespace std;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool f=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') f=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(f?x:~x+1);
}
int n,m,a,b,tot,ans,dfn[N],low[N];
bool v[N][N],again[N][N];
vector<int> e[M];
struct aa{int l,r;}s[M];
bool cmp(aa s1,aa s2){if(s1.l==s2.l) return s1.r<s2.r;return s1.l<s2.l;}
void tarjan(int x,int fa)
{
	dfn[x]=low[x]=++tot;
	for(int y:e[x])
		if(!dfn[y])
		{
			tarjan(y,x),
			low[x]=min(low[x],low[y]);
			if(low[y]>dfn[x]) s[++ans]=(aa){x,y};
		} 
		else if(y!=fa||again[x][y]) low[x]=min(low[x],dfn[y]);//重边一定不是割边
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
	read(n),read(m);
	while(m--) 
    {
        read(a),read(b);
        if(v[a][b]) 
        {
            again[a][b]=again[b][a]=1;//判断重边 
            continue;
        }
        v[a][b]=v[b][a]=1,
		e[a].push_back(b),
		e[b].push_back(a);
    }
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,i);
    stable_sort(s+1,s+1+ans,cmp);
    for(int i=1;i<=ans;i++) cout<<s[i].l<<' '<<s[i].r<<endl;
}
  • 样例输入
    7 10
    1 2
    2 3
    2 4
    3 4
    1 5
    5 6
    5 7
    7 8
    5 8
    6 5
  • 样例输出
    1 2
    1 5
    image
posted @ 2023-12-04 21:30  卡布叻_周深  阅读(32)  评论(0编辑  收藏  举报