描述
给定一个n个点m条边的无向连通图,求该图中“点双连通分量”的个数。
输入
第一行,n, m。
接下来m行,每行两个整数u,v,表示一条无向边(u,v)
对于100%的数据,2<=n,m<=2 * 10^5。
输出
输出点双连通分量的数量。
样例输入
5 6
1 2
2 3
1 3
3 5
4 5
3 4
样例输出
2
分析
模板题 主要理解点双连通分量的概念
相关概念分析
- 割点: 若无向图G中,存在一个点,当该点被删除时,图G中的连通分量数目增加。整张图不再连通。
- 连通分量:互相联通的子图
- 极大连通子图:连通图只有一个极大连通子图,就是它本身。(是唯一的)非连通图有多个极大连通子图。(非连通图的极大连通子图叫做连通分量,每个分量都是一个连通图)称为极大是因为如果此时加入任何一个不在图的点集中的点都会导致它不再连通。
- 点双连通:在一个无向图中,若任意两点间至少存在两条“点不重复”的路径。
- 点-双连通分量:一个子图满足点双连通且在图G中是极大(在满足点-双连通的所有子图中是极大的)
在本题中求点双连通分量需要设定一个栈,在寻找割点的时候将结点入栈,一旦发现割点就开始弹栈,但是不把当前割点弹出来(割点包含在多个点双里面)
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5+10;
int head[N],low[N],dfn[N],idx,cnt,stackk[N],top,num,flag[N];
vector<int>dc[N];
struct node
{
int ne,v;
}e[N*2];
void add(int a,int b)
{
e[++idx].v=b;
e[idx].ne=head[a];
head[a]=idx;
}
void tarjan(int u,int fa)
{
int child=0;
dfn[u]=low[u]=++cnt;
stackk[++top]=u;
if(u==fa && !head[u])//孤立点
{
dc[++num].push_back(u);
return;
}
for(int i=head[u];i;i=e[i].ne)
{
int v=e[i].v;
if(!dfn[v])
{
tarjan(v,fa);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u])
{
child++;
if(u!=fa || child>1)
flag[u]=1;
int temp;
num++;
while(temp=stackk[top--])//发现割点开始栈遍历
{
dc[num].push_back(temp);
if(temp==v)
break;
}
dc[num].push_back(u);
}
}
else
low[u]=min(low[u],dfn[v]);
}
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])
tarjan(i,i);
}
printf("%d\n",num);
return 0;
}