该算法是R.Tarjan发明的。对图深度优先搜索, dfn[i]为第i个结点在搜索树中的深度,low[i]为第i个结点的子树的所有儿子连接到的最上面的结点层数。根据定义,则有:
Low(u)=Min
{
dfn(u),
dfn(v) ,(u,v)为后向边(返祖边) 等价于 DFS(v)
一个顶点u是割点,当且仅当满足(1)或(2)
(1) u为树根,且u有多于一个子树。
(2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得DFS(u)<=Low(v)。
一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)
int root, cnt;
int vis[maxn], dfn[maxn], low[maxn];
bool cut[maxn];
//vector>bridge;
void dfs(int u, int fa)
{
int son=0;
vis[u]=1;
dfn[u]=low[u]=++cnt;
for (int i=0; iint v=G[u][i];
if (v==fa) continue;
if (vis[v]==1) low[u]=min(low[u], dfn[v]); //返祖边
if (vis[v]==0)
{
dfs(v, u);
son++;
low[u]=min(low[u], low[v]);
if ( (u==root && son>1) || (u!=root && low[v]>=dfn[u]))
{
cut[u]=true;
//if(low[v] > dfn[u]) bridge.push_back({u, v}); //(u, v) 是桥
}
}
}
vis[u]=2;
}
void tarjan_init()
{
memset(vis, 0, sizeof(vis));
memset(cut, 0, sizeof(cut));
cnt=0; root=1;
//bridge.clear();
}
POJ1144【基础】
题目大意:
给出一个无向图,求出有多少个割点。
输入:
有若干组测试数据。每一组测试数据的第一行有一个整数 n,表示有 n
(1<=n<100)个点,n=0 时测试数据结束。接下来有若干行,每一行第一个整
数 u 表示这一行描述的是以 u 为起点的边,接下来有若干个整数 vi 表示有一条
边 u-vi,u=0 时表示这一组测试数据结束。
输出:
对于每一组测试数据,输出一个整数,即有多少个割点。
模板题,没太多要说的
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int maxn=110;
vector<int> G[maxn], a;
char s[1000];
void add(int u, int v)
{
G[u].push_back(v);
G[v].push_back(u);
}
void deal()
{
a.clear();
int lens=strlen(s), now=0;
for (int i=0; iif (s[i]==' ')
{
a.push_back(now);
now=0;
}else now=now*10+(s[i]-'0');
a.push_back(now);
}
//tarjan
int root, cnt;
int vis[maxn], dfn[maxn], low[maxn];
bool cut[maxn];
//vector>bridge;
void dfs(int u, int fa)
{
int son=0;
vis[u]=1;
dfn[u]=low[u]=++cnt;
for (int i=0; iint v=G[u][i];
if (v==fa) continue;
if (vis[v]==1) low[u]=min(low[u], dfn[v]);
if (vis[v]==0)
{
dfs(v, u);
son++;
low[u]=min(low[u], low[v]);
if ( ((u==root) && son>1) || (u!=root && low[v]>=dfn[u]))
{
cut[u]=true;
//if(low[v] > dfn[u]) bridge.push_back({u, v}); //(u, v) 是桥
}
}
}
vis[u]=2;
}
void tarjan_init()
{
memset(vis, 0, sizeof(vis));
memset(cut, 0, sizeof(cut));
cnt=0; root=1;
//bridge.clear();
}
int main()
{
int n;
while (scanf("%d\n", &n)!=EOF)
{
if (n==0) break;
for (int i=1; i<=n; i++) G[i].clear();
int u, v;
while (1)
{
scanf("%[^\n]s", s); getchar();
deal();
for (int i=1; i0], a[i]);
if (a[0]==0) break;
}
tarjan_init();
dfs(1, -1);
int ans=0;
for (int i=1; i<=n; i++)
if (cut[i]) ans++;
printf("%d\n", ans);
}
return 0;
}
POJ1523
题目大意:
给出一个无向图,求出其割点的数量,并且求出去掉每一个割点后原图分成多
少个连通分量。
输入:
有若干组测试数据。每一组测试数据有若干行,每一行有两个整数表示无向图
中的一条边,或者一个 0 表示这一组测试数据的结束。所有测试数据最后以一
个 0 作为结束。无向图中的点的数量不超过 1000 个,假定一个图里面的点从 1
开始编号,并且是连续递增的。
输出:
对于每一组测试数据,如果有割点,按如下格式输出:
Network #测试数据组序号
SPF node 割点 1 编号 leaves x1 subnets
SPF node 割点 2 编号 leaves x2 subnets
……
其中 xi 表示在图中去掉某一个割点后产生的连通分量数量。
如果没有割点,输出:
Network #测试数据组序号
No SPF nodes
两组输出之间需要有一个空行进行分割。
题解:
先求割点,然后枚举每一个割点裸 dfs 求连通分量数量。
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int maxn=1010;
vector<int> G[maxn+10];
void add(int u, int v)
{
G[u].push_back(v);
G[v].push_back(u);
}
//tarjan
int root, cnt;
int vis[maxn], dfn[maxn], low[maxn];
bool cut[maxn];
//vector>bridge;
void dfs(int u, int fa)
{
int son=0;
vis[u]=1;
dfn[u]=low[u]=++cnt;
for (int i=0; iint v=G[u][i];
if (v==fa) continue;
if (vis[v]==1) low[u]=min(low[u], dfn[v]);
if (vis[v]==0)
{
dfs(v, u);
son++;
low[u]=min(low[u], low[v]);
if ( ((u==root) && son>1) || (u!=root && low[v]>=dfn[u]))
{
cut[u]=true;
//if(low[v] > dfn[u]) bridge.push_back({u, v}); //(u, v) 是桥
}
}
}
vis[u]=2;
}
void tarjan_init()
{
memset(vis, 0, sizeof(vis));
memset(cut, 0, sizeof(cut));
cnt=0; root=1;
//bridge.clear();
}
int n;
int belong[maxn], cnt2;
void dfs2(int u, int fa, int x)
{
if (belong[u]!=-1) return;
belong[u]=cnt2;
for (int i=0; i<(int)G[u].size(); i++)
{
int v=G[u][i];
if (v==fa || v==x) continue;
dfs2(v, u, x);
}
}
void deal(int x)
{
memset(belong, -1, sizeof(belong));
cnt2=0;
for (int i=1; i<=n; i++)
if (i!=x && belong[i]==-1) ++cnt2, dfs2(i, -1, x);
}
int main()
{
int a, b, kase=0;
while (scanf("%d", &a)!=EOF)
{
if (a==0) break;
kase++;
printf("Network #%d\n", kase);
for (int i=1; i<=1000; i++) G[i].clear();
scanf("%d", &b);
n=max(a, b);
add(a, b);
int u, v;
while (scanf("%d", &u)!=EOF)
{
if (u==0) break;
scanf("%d", &v);
add(u, v);
n=max(n, u); n=max(n, v);
}
tarjan_init();
dfs(1, -1);
bool ans=false;
for (int i=1; i<=n; i++) if (cut[i]) ans=true;
if (ans==false)
printf(" No SPF nodes\n");
else
for (int i=1; i<=n; i++)
{
deal(i);
if (cut[i]) printf(" SPF node %d leaves %d subnets\n", i, cnt2);
}
printf("\n");
}
return 0;
}
POJ3694
题目大意:
给出一个无向图,有 N 个点和 M 条边( 1<=N<=100000, N-1<=M<=200000)求出
有多少条割边(桥),以及每加入一条新的边以后还剩下多少条割边。假定所
有的点从 1 到 N 进行编号,一开始的时候全图是连通的。
输入:
有若干组测试数据。每一组测试数据的第一行有两个整数 N 和 M,当 N=M=0 时
输入数据结束。接下来有 M 行,每一行有两个整数 u, v,表示在 u, v 之间有
一条边。接下来有一行,包含一个整数 Q,表示将加入多少条边。接下来有 Q
行,每一行按上述相同方式描述一条边。
输出:
对于每一组测试数据,先输出一行“ Case %测试数据组编号%:”,然后对每一
条新加入的边,输出一行,包含一个整数,即加入了这一条边以后还有多少条
割边。
问题分析:
如下图,如若加了绿边,带来的影响是从绿边(u,v)往上的节点,直到lca(u,v)都不再是割点
用并查集处理即可,这里的lca不用写O(log n)的版本,O(n)的即可
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int maxn=100100;
vector<int> G[maxn+10];
void add(int u, int v)
{
G[u].push_back(v);
G[v].push_back(u);
}
//tarjan
int root, cnt, ans;
int vis[maxn], dfn[maxn], low[maxn];
bool bridge[maxn];
int parent[maxn];
void dfs(int u, int fa)
{
int son=0;
vis[u]=1;
dfn[u]=low[u]=++cnt;
for (int i=0; i<(int)G[u].size(); i++)
{
int v=G[u][i];
if (v==fa) continue;
if (vis[v]==1) low[u]=min(low[u], dfn[v]);
if (vis[v]==0)
{
dfs(v, u);
parent[v]=u;
son++;
low[u]=min(low[u], low[v]);
if ( (u==root && son>1) || (u!=root && low[v]>=dfn[u]) )
{
ans++;
bridge[v]=1;
}
}
}
vis[u]=2;
}
void tarjan_init()
{
memset(vis, 0, sizeof(vis));
memset(bridge, 0, sizeof(bridge));
cnt=0; root=1; ans=0;
}
void lca(int u, int v)
{
if (dfn[v]while (dfn[v]>dfn[u])
{
if(bridge[v]) {
ans--;
bridge[v] = 0;
}
v=parent[v];
}
while(u!= v)
{
if (bridge[u]) {
ans--;
bridge[u]=0;
}
u=parent[u]; v=parent[v];
}
}
int main()
{
int n, m, kase=0;
while (scanf("%d%d", &n, &m)!=EOF)
{
if (n==0 && m==0) break;
printf("Case %d:\n", ++kase);
for (int i=1; i<=m; i++)
{
int u, v;
scanf("%d%d", &u, &v);
add(u, v);
}
tarjan_init();
dfs(1, -1);
int Q;
scanf("%d", &Q);
while (Q--)
{
int u, v;
scanf("%d%d", &u, &v);
lca(u, v);
printf("%d\n", ans);
}
printf("\n");
}
return 0;
}