20251227 - 点双 割点 割边

20251227 - 点双 割点 割边

前言

Good Bye 2025,我来了!!!

You have no egg!

连通分量(极大连通子图)

子图就是从原图里面选出一些点和一些边,组成一个新的图,就叫原图的子图。

连通子图就是要满足整张子图连通。

极大连通子图就是要使得连通子图的点和边数量尽可能大,注意是极大,不是最大。

极大:就是再加一个点或边就不满足条件了!

割点

对于一个无向图,如果把一个点删除后这个图的极大连通分量数增加了,那么这个点就是这个图的割点(又称割顶)。

首先,我们上一个图:

img

我们可以发现,割点是 2,而且这个图仅有这一个割点。

首先,我们给他打上时间戳。

img

如果对于一个点 \(u\),存在一个儿子 \(v\) 不能回到比 \(u\) 更高的点,那么 \(u\) 就是割点

因为删掉 \(u\) 之后,\(v\) 所在的子树没有反祖边可以连回来。

img

但是如果是根节点呢?

如果根节点在搜索树上有两个或更多子节点,根节点是割点。

否则如果只有一个子节点,则不是割点

代码:

inline void tarjan(int u, int fa) {
	dfn[u] = low[u] = ++idx;
	bool flag = false;
	int cnt = 0;
	for (auto v : edges[u]) {
		if (!dfn[v]) {
			cnt++;
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u] && u != fa && !flag) {
				flag = 1;
				c.push_back(u);
			}
		}else {
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (!flag && u == fa && cnt > 1) {
		c.push_back(u);
	}
}

割边

和割点差不多,叫做桥。

对于一个无向图,如果删掉一条边后图中的连通分量数增加了,则称这条边为桥或者割边。

比如说,下图中,

割边示例图

红色的边就是割边。

如果没有重边,只需要简单的把求割点的过程中的 \(low_v \ge dfn_u\) 改为 \(>\) 即可,

如果有重边,那么重边们一定不是桥。

我们忽略第一条边即可。

代码:

inline void tarjan(int u, int fa) {
  dfn[u] = low[u] = ++idx;
  int mark = 0, flag = 0;
  for (auto [v, id] : edges[u]) {
    if (v == fa && !mark) {
      mark = 1;
    }else if (!dfn[v]) {
      tarjan(v, u);
      low[u] = min(low[u], low[v]);
      if (low[v] > dfn[u]) {
        flag = 1;
        ans.push_back(id);
      }
    }else {
      low[u] = min(low[u], dfn[v]);
    }
  }
}

点双连通分量

删去一个点后连通性不变的连通分量就是点双连通分量,简称点双。

如图:

bcc-counterexample.png

  • 两个点双之间最多只有一个公共点,这个点是割点。

  • 对于一个点双,它在搜索树中 \(dfn\) 最小的点一定是割点或根节点

所以,我们用栈记录搜索过的点,在搜索到割点之后的回溯段,把点双统计出来即可。

注意:孤点

代码:

inline void tarjan(int u, int fa) {
	dfn[u] = low[u] = ++idx;
	stk[++top] = u;
	for (auto v : edges[u]) {
    if (v == fa) continue;
		if (!dfn[v]) {	
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u]) {
				vector <int> ve{u};
        cnt[u]++;
        while (ve.back() != v) {
          ve.push_back(stk[top]);
          cnt[stk[top]]++;
          top--;
        }
        bcc.pb(ve);
			}
		}else {
			low[u] = min(low[u], dfn[v]);
		}
	}
  if (u == fa && cnt[u] == 0) {
    bcc.pb({u});
  }
}

例题:

G - 嗅探器

如果 \(u\) 是割点,且将 \(A\)\(B\) 两点割在两边,则此点可行。

如果 \(dfn_v \le dfn_B\),就可行,统计答案即可!

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
const int N = 5e5 + 7;
const int P = 998244353;
int read() {
  int x = 0, f = 1;
  char ch = getchar();
  while (!(ch >= '0' && ch <= '9')) {if (ch == '-') f = -f;ch = getchar();}
  while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
  return x * f;
}
int n, m, a, b;
vector <int> edges[N], ans;
int dfn[N], low[N], idx;
inline void tarjan(int u, int fa) {
  dfn[u] = low[u] = ++idx;
  bool flag = false;
  int cnt = 0;
  for (auto v : edges[u]) {
    if (!dfn[v]) {
      tarjan(v, u);
      low[u] = min(low[u], low[v]);
      cnt ++;
      if (low[v] >= dfn[u] && !flag && u != fa && dfn[b] >= dfn[v]) {
        flag = 1;
        ans.push_back(u);
      }
    }else {
      low[u] = min(low[u], dfn[v]);
    }
  } 
  if (!flag && u == fa && cnt > 1) {
    ans.push_back(u);
  }
}
int fa[N];
int find(int x) {
  if (fa[x] == x) return x;
  return fa[x] = find(fa[x]);
}
void unite(int x, int y) {
  x = find(x), y = find(y);
  if (x != y) fa[x] = y;
}
void solve() {
  n = read();
  int x, y;
  for (int i = 1; i <= n; i++) fa[i] = i;
  while (scanf("%d%d", &x, &y)) {
    if (x == 0 && y == 0) break;
    m++; 
    unite(x, y);
    edges[x].pb(y);
    edges[y].pb(x);
  }
  if (find(x) != find(y)) {
    puts("No solution");
    return;
  }
  a = read(), b = read();
  for (int i = 1; i <= n; i++) {
    if (!dfn[i]) {
      tarjan(i, i);
    }
  }
  if (ans.size() == 0) {
      puts("No solution");
      return;
  }
  sort(ans.begin(), ans.end());
  printf("%d\n", *ans.begin());
}
int main() {
  int oT_To = 1;
  while (oT_To--) solve();
  return 0;
}
posted @ 2025-12-27 22:18  AKCoder  阅读(5)  评论(0)    收藏  举报