点双连通和边双连通
连通的概念:在无向图中,所有点能互相到达
连通分量:互相联通的子图
点双连通:删掉一个点之后,图仍联通
边双连通:删掉一条边之后,图仍联通
tarjan 算法:
该算法是R.Tarjan发明的。对图深度优先搜索, dfn[i]为第i个结点在搜索树中的深度,low[i]为第i个结点的子树的所有儿子连接到的最上面的结点层数。根据定义,则有:
一个顶点u是割点,当且仅当满足(1)或(2)
(1) u为树根,且u有多于一个子树。
(2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得DFS(u)<=Low(v)。
一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)
求双连通分量
对于点双连通分量,实际上在求割点的过程中就能顺便把每个点双连通分量求出。建立一个栈,存储当前双连通分量,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满足DFS(u)<=Low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分量,其余点和每条边只属于且属于一个点双连通分量支。
(这种还没有实现过,不过我认为显然如果把割点标记出来,跑dfs也能求出点双连通分支,虽然代码量会上升,不过还挺好打的,下面例题二类似)
对于边双连通分量,求法更为简单。只需在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分量。桥不属于任何一个边双连通分量,其余的边和每个顶点都属于且只属于一个边双连通分量。
模板:
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; i1) || (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();
}
例题:
poj-1144
题目大意:
给出一个无向图,求出有多少个割点。
输入:
有若干组测试数据。每一组测试数据的第一行有一个整数 n,表示有 n
(1<=n<100)个点,n=0 时测试数据结束。接下来有若干行,每一行第一个整
数 u 表示这一行描述的是以 u 为起点的边,接下来有若干个整数 vi 表示有一条
边 u-vi,u=0 时表示这一组测试数据结束。
输出:
对于每一组测试数据,输出一个整数,即有多少个割点。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define N 1005
vectora[N],m;
int n, cnt, root;
char str[N];
int dfn[N], low[N], vis[N], cut[N];
void init() {
cnt = 0; root = 1;
memset(cut, 0, sizeof(cut));
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; i++)
a[i].clear();
}
void dfs(int u, int fa) {
int child = 0;
cut[u] = 1;
dfn[u] = low[u] = ++cnt;
for (int i = 0; i < a[u].size(); i++) {
int v = a[u][i];
if (v == fa)continue;
if (cut[v]==0) {
dfs(v, u);
child++;
low[u] = min(low[u], low[v]);
if ((u == root && child > 1) || (u != root && low[v] >= dfn[u])) {
vis[u] = 1;
}
}
if(cut[v]==1) {
low[u] = min(low[u], dfn[v]);
}
}
cut[u] = 2;
}
void deal()
{
m.clear();
int lens = strlen(str), now = 0;
for (int i = 0; i
poj-1523
题解:
先求割点,然后枚举每一个割点裸 dfs 求连通分量数量。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define N 1100
int s, e, n;
int cnt, root;
int dfn[N], low[N], re[N], vis[N];
int res[N];
int flag;
vectora[N];
void init() {
root = 1; cnt = 0;
memset(vis, 0, sizeof(vis));
memset(dfn, 0, sizeof(dfn));
}
void dfs(int u, int fa) {
int child;
child = 0;
dfn[u] = low[u] = ++cnt;
for (int i = 0; i < a[u].size(); i++) {
int v = a[u][i];
if (v == fa)continue;
if (!dfn[v]) {
child++;
dfs(v, u);
low[u] = min(low[u], low[v]);
if ((u == root && child > 1) || (u != root && low[v] >= dfn[u])) {
flag = 1;
vis[u] = 1;
}
}
else {
low[u] = min(dfn[v], low[u]);
}
}
}
void bfs(int u) {
queueq;
q.push(u);
while (!q.empty()) {
int cmt = q.front();
q.pop();
for (int i = 0; i < a[cmt].size(); i++) {
int to = a[cmt][i];
if (!res[to]) {
res[to] = 1;
q.push(to);
}
}
}
}
int find(int u) {
int ans = 0;
memset(res, 0, sizeof(res));
res[u] = 1;
for (int i = 1; i <= n; i++) {
if (!res[i]) {
ans++;
res[i] = 1;
bfs(i);
}
}
return ans;
}
int main() {
int t = 1;
while (t++) {
flag = 0;
n = -1;
init();
cin >> s;
if (s == 0)break;
cin >> e;
a[s].push_back(e);
a[e].push_back(s);
n = max(n, max(s, e));
while (1) {
cin >> s;
if (s == 0)break;
cin >> e;
a[s].push_back(e);
a[e].push_back(s);
n = max(n, max(s, e));
}
dfs(root, -1);
if (!flag) {
cout << "Network #" << t-1 << endl;
cout << " No SPF nodes" << endl << endl;
for (int i = 1; i <= n; i++)
a[i].clear();
continue;
}
cout << "Network #" << t-1 << endl;
for (int i = 1; i <= n; i++) {
if (vis[i]) {
cout << " SPF node " << i << " leaves " << find(i) << " subnets" << endl;
}
}
cout << endl;
for (int i = 1; i <= n; i++)
a[i].clear();
}
return 0;
}
poj-3694
求双连通分量,利用并查集缩点,形成一棵树,树边肯定都是桥,然后每对点x,y,找原图中x,y点对应的新图中的点,如果不是一个点,则向上找它们的LCA,因为它们之间连了一条边,所以这些点到它们的LCA之间的边都不是割边了,找LCA时,先将两点上升到同一层次,然后一起再向上找父亲节点,其间遇到桥就把桥的标记删除,并且答案减1。
#include
#include
#include
#include
#include
#include
using namespace std;
#define N 100005
int n, m, s, e, k, root;
int cnt;
int head[N], low[N], dfn[N], vis[N], cmt[N], parent[N];
int df[N];
int ans;
struct eg {
int to, st, next;
}a[4*N];
void dfs(int u, int fa, int index) {
df[u] = index;
dfn[u] = low[u] = ++cnt;
vis[u] = 1;
for (int i = head[u]; i != -1; i = a[i].next) {
int v = a[i].to;
if (v == fa)continue;
if (vis[v] == 1) {
low[u] = min(low[u], dfn[v]);
}
if (vis[v] == 0) {
dfs(v, u, index + 1);
parent[v] = u;
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u]) {
cmt[v] = 1;
ans++;
}
}
}
vis[u] = 2;
}
void init() {
root = 1; cnt = 0; ans = 0;
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
memset(cmt, 0, sizeof(cmt));
}
void add(int u, int v) {
a[cnt].st = u;
a[cnt].to = v;
a[cnt].next = head[u];
head[u] = cnt++;
a[cnt].st = v;
a[cnt].to = u;
a[cnt].next = head[v];
head[v] = cnt++;
}
int find(int u, int v) {
int sum = 0;
if (df[u] > df[v])swap(u, v);
while (df[u] < df[v]) {
if (cmt[v]) {
sum++;
cmt[v] = 0;
}
v = parent[v];
}
while (u != v) {
if (cmt[u]) {
sum++;
cmt[u] = 0;
}
if (cmt[v]) {
sum++;
cmt[v] = 0;
}
v = parent[v];
u = parent[u];
}
return sum;
}
int main() {
int nu = 1;
while (~scanf("%d%d", &n, &m)) {
if (n == 0 & n == m)break;
init();
for (int i = 0; i < m; i++) {
scanf("%d%d", &s, &e);
add(s, e);
}
cnt = 0;
dfs(root, -1, 1);
scanf("%d", &k);
cout << "Case " << nu++ << ":" << endl;
for (int i = 0; i < k; i++) {
scanf("%d%d", &s, &e);
ans -= find(s, e);
cout << ans << endl;
}
}
return 0;
}