双联通分量
定义
割点:给定一无向连通图,对于其中一点 ,若从图中删掉 和所有与 相连的边后,原图分裂成成 个或以上不相连的子图,则称 为原图的割点(或割顶)。
割边:给定一无向连通图,对于其中一边 ,若从图中删掉 后,原图分裂成 个或以上不相连的子图,则称 为原图的割边(或桥)。
点双联通分量:一张无向连通图,若不存在割点,且任意两点间均有两条或以上的点不重复路径,则称为:点双联通分量。
边双联通分量:一张无向连通图,若不存在割边,且任意两点间均有两条或以上的边不重复路径,则称为:边双联通分量。
性质
点双联通分量:
- 对于一个割点,他应该存在于 个及多个点双联通分量内。
- 对于不是割点的点,他只能存在于一个点双联通分量中。
- 除了两点一线的情况,其余的点双连通分量一定是边双连通分量,反之不一定。
- 图 中的边无论是否是桥,都最多属于一个点双连通分量;
- 对于一个点双联通分量中的任意两个点,它们之间都有至少两条点不重复的路径。
边双联通分量:
- 割边不属于任意边双联通分量,而其它非割边的边都属于且仅属于一个边双联通分量。
- 对于一个边双联通分量中的任意两个点,它们之间都有至少两条边不重复的路径。
- 当一个双连通分量中的边数大于点数时,其中所有的边都属于两个及以上的环。
- 对于一连通的无向图,其桥的数量一定等于边双连通分量的数量 。
点双联通分量
对于求点双连通分量的方法,我比较推荐 “弹点法”:
假设我们遍历到了无向边 且点 是割点,再想到关于割点的定理:
对于一个割点,他应该存在于 个及多个点双联通分量内。
如果 是割点,那么我们把 删掉,原图就被分成了 和 的子树 和 剩下的节点 至少 个子图。
如果遇到 dfn[u] <= low[v]
,那么此时, 和 的子树 和 就是一个点双联通分量。
然后我们将 和 的子树弹出并记录。
注意:此时,不能将 弹出,根据 点双联通分量的定义(图 点双连通的极大子图), 可能存在于多个点双联通分量之中。
重要:对于度为 的点,需要特判,因为:孤点也是一个点双联通分量。
#include <bits/stdc++.h>
using namespace std;
struct Fastio
{
template <typename T>
inline Fastio operator>>(T &x)
{
x = 0;
char c = getchar();
while (c < '0' || c > '9')
c = getchar();
while (c >= '0' && c <= '9')
x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
return *this;
}
inline Fastio &operator<<(const char *str)
{
int cur = 0;
while (str[cur])putchar(str[cur++]);
return *this;
}
template <typename T>
inline Fastio &operator<<(T x)
{
if (x == 0)
{
putchar('0');
return *this;
}
if (x < 0) putchar('-'), x = -x;
static int sta[45];
int top = 0;
while (x) sta[++top] = x % 10, x /= 10;
while (top) putchar(sta[top] + '0'), --top;
return *this;
}
} io;
int n, m, rt, ans, cnt_node, cntn;
int cnt;
array<int, 2000005> head;
struct abc
{
int to, nxt;
};
array<abc, 2000005> dd;
array<int, 2000005> dfn, low;
stack<int> s;
vector<int> cutt[2000005];
inline void add(int u, int v)
{
dd[++cnt].to = v;
dd[cnt].nxt = head[u];
head[u] = cnt;
}
inline void tarjan(int u)
{
dfn[u] = low[u] = ++cnt_node;
s.push(u);
// int flag = 0;
if(rt == u && !head[u])
{
cntn++;
cutt[cntn].push_back(u);
return;
}
for (int e = head[u]; e; e = dd[e].nxt)
{
int v = dd[e].to;
if (!dfn[v])
{
tarjan(v);
low[u] = min(low[v], low[u]);
if(dfn[u] <= low[v])
{
// cout << v << endl;
// flag++;
// if(u != rt || flag > 1)
// {
cntn++;
while(1)
{
int now = s.top();
s.pop();
cutt[cntn].push_back(now);
if(v == now) break;
}
cutt[cntn].push_back(u);
// }
}
}
else low[u] = min(low[u], dfn[v]);
}
}
signed main()
{
io >> n >> m;
for(int i = 1; i <= m; ++i)
{
int u, v;
io >> u >> v;
add(u, v);
add(v, u);
}
for(int i = 1; i <= n; ++i)
if(!dfn[i]) tarjan(rt = i);
for(int i = 1; i <= cntn; ++i)
{
for(int j = 0; j < cutt[i].size(); ++j)
{
cout << cutt[i][j] << " ";
}
cout << endl;
}
return 0;
}
边双联通分量
对于求边双连通分量的方法,我比较推荐 法:
用 数组记录边有没有被遍历过,每遇到一条没遍历到的边,就将他这条边和他的反边都标记为 。
若已知一条边的编号为 ,则他的反边的编号 的计算方法:
- 若 ,;
- 若 ,;
当点 的 结束后,如果 ,说明 的子树中没有后向边。此时不断地弹出栈顶的点,标记其所属的边双连通分量,直到 出栈。
给定一个无向图,试求最少要加入几条边,才能使得该图变成一个双连通图。
给定一个无向图,至少还要修建多少条道路, 才能使得任意一条道路被占领的情况下, 其任意两个城市都可以互相到达?且如果一条道路被占领,所有重边都会被占领。
思路:
Tarjan
缩点,新图中度为 的节点数 。
新图中通向度为 的节点的边即为桥,切断则图不连通。
所以要使每个点的度都大于 。
连接两个度为 的节点可以同时解决它们。
如果有剩余的点就特供给它一条边。
例题
#include<bits/stdc++.h>
using namespace std;
struct Fastio
{
template <typename T>
inline Fastio operator>>(T &x)
{
x = 0;
char c = getchar();
while (c < '0' || c > '9')
c = getchar();
while (c >= '0' && c <= '9')
x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
return *this;
}
inline Fastio &operator<<(const char *str)
{
int cur = 0;
while (str[cur])
putchar(str[cur++]);
return *this;
}
template <typename T>
inline Fastio &operator<<(T x)
{
if (x == 0)
{
putchar('0');
return *this;
}
if (x < 0)
putchar('-'), x = -x;
static int sta[45];
int top = 0;
while (x)
sta[++top] = x % 10, x /= 10;
while (top)
putchar(sta[top] + '0'), --top;
return *this;
}
} io;
#define _ 20005
int n, m, ans;
int tot, head[_], to[_ << 1], nxt[_ << 1];
int dol[_];
int cnt_node, cntn, low[_], dfn[_], id[_], vis[_ << 1];
stack<int> s;
int u[_], v[_];
int js(int x)
{
return (x % 2) ? x + 1 : x - 1;
}
void add(int u, int v)
{
to[++tot] = v;
nxt[tot] = head[u];
head[u] = tot;
}
void tarjan(int u)
{
low[u] = dfn[u] = ++cnt_node;
s.push(u);
for(int i = head[u]; i; i = nxt[i])
if(!vis[i])
{
vis[i] = vis[js(i)] = 1;
if(!dfn[to[i]])
{
tarjan(to[i]);
low[u] = min(low[u], low[to[i]]);
}
else low[u] = min(low[u], dfn[to[i]]);
}
if(dfn[u] == low[u])
{
cntn++;
while(1)
{
int now = s.top();
s.pop();
id[now] = cntn;
if(now == u) break;
}
}
}
signed main()
{
// freopen("P2860_2.in", "r", stdin);
// freopen("2860_2.ans", "w", stdout);
io >> n >> m;
for(int i = 1; i <= m; ++i)
{
io >> u[i] >> v[i];
add(u[i], v[i]);
add(v[i], u[i]);
}
for(int i = 1; i <= n; ++i)
if(!dfn[i]) tarjan(i);
for(int i = 1; i <= m; ++i)
{
if(id[u[i]] != id[v[i]])
{
dol[id[u[i]]]++;
dol[id[v[i]]]++;
}
}
for(int i = 1; i <= cntn; ++i)
if(dol[i] == 1) ans++;
io << (ans + 1) / 2 << "\n";
}
例题
#include<bits/stdc++.h>
using namespace std;
struct Fastio
{
template <typename T>
inline Fastio operator>>(T &x)
{
x = 0;
char c = getchar();
while (c < '0' || c > '9')
c = getchar();
while (c >= '0' && c <= '9')
x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
return *this;
}
inline Fastio &operator<<(const char *str)
{
int cur = 0;
while (str[cur])
putchar(str[cur++]);
return *this;
}
template <typename T>
inline Fastio &operator<<(T x)
{
if (x == 0)
{
putchar('0');
return *this;
}
if (x < 0)
putchar('-'), x = -x;
static int sta[45];
int top = 0;
while (x)
sta[++top] = x % 10, x /= 10;
while (top)
putchar(sta[top] + '0'), --top;
return *this;
}
} io;
#define _ 20005
int n, m, ans;
int tot, head[_], to[_ << 1], nxt[_ << 1];
int dol[_];
int cnt_node, cntn, low[_], dfn[_], id[_], vis[_ << 1];
stack<int> s;
int u[_], v[_];
bool opt[_ << 1];
bool flagg[2001][2001];
int js(int x)
{
return (x % 2) ? x + 1 : x - 1;
}
void add(int u, int v)
{
to[++tot] = v;
nxt[tot] = head[u];
head[u] = tot;
}
void tarjan(int u)
{
low[u] = dfn[u] = ++cnt_node;
s.push(u);
for(int i = head[u]; i; i = nxt[i])
if(!vis[i])
{
vis[i] = vis[js(i)] = 1;
if(!dfn[to[i]])
{
tarjan(to[i]);
low[u] = min(low[u], low[to[i]]);
}
else low[u] = min(low[u], dfn[to[i]]);
}
if(dfn[u] == low[u])
{
cntn++;
while(1)
{
int now = s.top();
s.pop();
id[now] = cntn;
if(now == u) break;
}
}
}
signed main()
{
// freopen("缩点D题data4.in","r",stdin);
io >> n >> m;
for(int i = 1; i <= m; ++i)
{
io >> u[i] >> v[i];
if(!flagg[u[i]][v[i]] && !flagg[v[i]][u[i]])
{
add(u[i], v[i]);
add(v[i], u[i]);
flagg[u[i]][v[i]] = flagg[v[i]][u[i]] = 1;
opt[i] = 1;
}
}
// cout<<endl;
for(int i = 1; i <= n; ++i)
if(!dfn[i]) tarjan(i);
for(int i = 1; i <= m; ++i)
{
if(id[u[i]] != id[v[i]] && opt[i]) dol[id[u[i]]]++, dol[id[v[i]]]++;
}
for(int i = 1; i <= cntn; ++i)
if(dol[i] == 1) ans++;
io << (ans + 1) / 2 << "\n";
}
本文来自博客园,作者:蒟蒻orz,转载请注明原文链接:https://www.cnblogs.com/orzz/p/18122146
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】