求割点 :
注意在求割点的时候,假如u的邻接点v已经被访问过,那么low【u】=min(low【u】,num【v】)!!
#include<bits/stdc++.h>
using namespace std;
const int maxn=20000+10;
const int maxm=100000+10;
struct Edge
{
int before;
int to;
}e[maxm<<1];
int n,m,k,head[maxn],num[maxn],low[maxn];
int tot; // 记录割点的个数
int tim; // 时间戳
bool cut[maxn]; // 判断该点是否为割点
void add(int u,int v)
{
e[k].to=v;
e[k].before=head[u]; //链式前向星存图
head[u]=k++;
}
void tarjan(int cur,int fa) // fa 为cur 的父节点
{
int child=0;
num[cur]=low[cur]=++tim;
for (int i=head[cur];i!=-1;i=e[i].before)
{
int v=e[i].to; //边终点
if(num[v]==0)// 如果没被访问过,那么访问它
{
child++;
tarjan(v,cur);
low[cur]=min(low[cur],low[v]);//当前节点能够访问的点所能得到的最小时间戳可以更新当前节点能够访问的最小时间戳,
if(low[v]>=num[cur]&&cur!=fa) // cur不是根节点
cut[cur]=1;
if(child>=2&&cur==fa) //对于根节点进行这个判断只有根节点cur==fa
cut[cur]=1; //有两个及以上子节点的根节点是割点
}
else if(v!=fa) // v不是父节点且v已经被访问过
low[cur]=min(low[cur],num[v]); // 直接更新 cur所能到达的最早时间戳
}
}
int main()
{
memset(head,-1,sizeof head);
k=0;
tot=0;
tim=0;
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(num[i]==0)
tarjan(i,i); // 原图可能本身由多个连通分量组成 故可能存在多个根节点
for(int i=1;i<=n;i++)
if(cut[i])
tot++;
printf("%d\n",tot);
for(int i=1;i<=n;i++)
if(cut[i])
printf("%d ",i);
return 0;
}
无向图找桥
// 一般调用 Tarjan(1,0)
void Tarjan(int u, int fa)
{
low[u] = dfn[u] = ++tim;
for (int i = head[u]; i != -1; i = e[i].before)
{
int v = e[i].to;
if (v == fa)
continue;
if (!dfn[v])
{
Tarjan(v, u);
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u])
ans++;
}
else
low[u] = min(low[u], dfn[v]);
}
}
缩点
void tarjan(int u)
{
low[u] = num[u] = ++idx;
st[++top] = u;
vis[u] = true; // 已经入栈
for (int i = head[u]; i != -1; i = e[i].before)
{
int v = e[i].to;
if (!num[v])
{
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if (vis[v])
{
low[u] = min(low[u], dfn[v]);
}
}
if (num[u] == low[u]) // 找到一个强连通分量
{
++cnt; // 强连通分量编号
int t;
while (t = st[top--])
{
belong[t] = cnt;
++size[cnt]; // 记录强联通分量大小
vis[t] = 0; // 入栈之后被出栈了
if (u == t)
break;
}
}
}
Tarjan找环 !蓝桥杯发现环!
题意: 图上有n个点,n-1条边,原图是一个最小生成树,现在加上一条边之后,那么必定会产生一个环了,现在要把这个环上的所有点找出来,从小到大输出。
上代码吧,没啥好说的。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<set>
using namespace std;
const int maxn=100000+10;
struct Edge
{
int before;
int to;
}e[maxn<<2];
int n,k,x,y,tim,head[maxn],num[maxn],pre[maxn];
set<int> s;
void add(int u,int v)
{
e[k].before=head[u];
e[k].to=v;
head[u]=k++;
}
bool tarjan(int u,int fa)
{
num[u]=++tim;
for(int i=head[u];i!=-1;i=e[i].before)
{
int v=e[i].to;
if(v==fa)
{
continue;
}
if(!num[v])
{
pre[v]=u;
tarjan(v,u);
}
if(num[v]>=1&&num[v]<num[u])
{
int x=u;
s.insert(v);
while(x!=v)
{
s.insert(x);
x=pre[x];
}
return true;
}
}
return false;
}
int main()
{
memset(head,-1,sizeof head);
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d %d",&x,&y);
add(x,y);
add(y,x);
}
pre[1]=0;
tarjan(1,0);
set<int>::iterator it;
for(it=s.begin();it!=s.end();it++)
printf("%d ",*it);
return 0;
}