边双连通分量

\(\text{luogu-8436}\)

对于一个 \(n\) 个节点 \(m\) 条无向边的图,求其边双连通分量的个数,并且输出每个边双连通分量。

\(1 \le n \le 5 \times 10^5\)\(1 \le m \le 2 \times 10^6\)


以下题解部分来自于 强连通分量 | 点双连通分量 | 边双连通分量 - 知乎

在一张连通的无向图中,对于任意的两个顶点 \(u\)\(v\) ,如果任意去掉一条边后,\(u\)\(v\) 之间的连通性仍然没有发生改变,那么 \(u\)\(v\) 就是边双连通的。对于一张无向图,其边双连通的极大子图,就称为边双连通分量

对于求解具体的边双连通分量,我们可以先求出无向图的割边,之后再一次遍历这张图,跳过割边,这样我们就可以求得具体的边双连通分量了。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define MAXN 500005
#define MAXM 2000005
#define pii pair<long long, long long>
#define fi first
#define se second

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

long long n, m, dfn[MAXN], low[MAXN], dn;
vector<vector<long long> > scc;
bool f[MAXM], vis[MAXN];
vector<pii > v[MAXN];

void tarjan(long long x, long long id) {
	dfn[x] = low[x] = ++ dn;
	for(auto it : v[x]) {
		long long y = it.fi, d = it.se;
		if(!dfn[y]) tarjan(y, d), low[x] = min(low[x], low[y]);
		else if(d != id) low[x] = min(low[x], dfn[y]);
	}
	if(dfn[x] == low[x] && id != -1) f[id] = 1;
	return;
}

void dfs(long long x) {
	vis[x] = 1;
	scc.back().push_back(x);
	for(auto it : v[x]) if(!f[it.se] && !vis[it.fi]) dfs(it.fi);
	return;
}

int main() {
	n = read(), m = read();
	for(int i = 1; i <= m; i ++) {
		long long x = read(), y = read();
		v[x].push_back({y, i}), v[y].push_back({x, i});
	}
	for(int i = 1; i <= n; i ++) if(!dfn[i]) tarjan(i, -1);
	for(int i = 1; i <= n; i ++) if(!vis[i]) scc.push_back({}), dfs(i);
	cout << scc.size() << "\n";
	for(auto it : scc) {
		cout << it.size() << " ";
		for(auto x : it) cout << x << " ";
		cout << "\n";
	}
	return 0;
}

\(\text{luogu-2860}\)

为了从 \(F\) 个牧场(编号为 \(1\)\(F\))中的一个到达另一个牧场,贝西和其他牛群被迫经过腐烂苹果树附近。奶牛们厌倦了经常被迫走特定的路径,想要修建一些新路径,以便在任意一对牧场之间总是有至少两条独立的路线可供选择。目前在每对牧场之间至少有一条路径,他们希望至少有两条。当然,他们只能在官方路径上从一个牧场移动到另一个牧场。

给定当前 \(R\) 条路径的描述,每条路径恰好连接两个不同的牧场,确定必须修建的最少新路径数量(每条新路径也恰好连接两个牧场),以便在任意一对牧场之间至少有两条独立的路线。若两条路线不使用相同的路径,即使它们沿途访问相同的中间牧场,也被视为独立的。

在同一对牧场之间可能已经有多条路径,你也可以修建一条新路径连接与某条现有路径相同的牧场。

\(1\le F\le 5000\)\(F-1\le R\le 10^4\)


首先我们发现,对于边双连通分量其实不需要考虑,缩点即可。

缩点后图变成了一颗树,我们只需要把叶子节点两两连边,就可以使得原图是一个极大边双连通分量。需要注意的是,若叶子节点为奇数个,需要连一半多一条边,实际上就是上取整。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define MAXN 10005
#define pii pair<long long, long long>
#define fi first
#define se second

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

long long n, m, dfn[MAXN], low[MAXN], dn, c[MAXN], d[MAXN], ans;
vector<vector<long long> > scc;
bool f[MAXN], vis[MAXN];
vector<pii > v[MAXN];

void tarjan(long long x, long long id) {
	dfn[x] = low[x] = ++ dn;
	for(auto it : v[x]) {
		long long y = it.fi, d = it.se;
		if(!dfn[y]) tarjan(y, d), low[x] = min(low[x], low[y]);
		else if(d != id) low[x] = min(low[x], dfn[y]);
	}
	if(dfn[x] == low[x] && id != -1) f[id] = 1;
	return;
}

void dfs(long long x) {
	vis[x] = 1, c[x] = scc.size();
	scc.back().push_back(x);
	for(auto it : v[x]) if(!f[it.se] && !vis[it.fi]) dfs(it.fi);
	return;
}

int main() {
	n = read(), m = read();
	for(int i = 1; i <= m; i ++) {
		long long x = read(), y = read();
		v[x].push_back({y, i}), v[y].push_back({x, i});
	}
	for(int i = 1; i <= n; i ++) if(!dfn[i]) tarjan(i, -1);
	for(int i = 1; i <= n; i ++) if(!vis[i]) scc.push_back({}), dfs(i);
	for(int i = 1; i <= n; i ++) for(auto it : v[i])
		if(c[i] != c[it.fi]) d[c[i]] ++, d[c[it.fi]] ++;
	for(int i = 1; i <= scc.size(); i ++) if(d[i] == 2) ans ++;
	cout << (ans + 1) / 2 << "\n";
	return 0;
}

\(\text{luogu-2783}\)

给定 \(n\) 个点 \(m\) 条边无向图,把图中所有的环变为一个点,求变化后某两个点之间有多少个点。

两个点不成环,且输出答案时转为二进制。

\(1<n\le10 ^ 4\)\(1<m\le5\times 10 ^ 4\)


有点奇怪的一道题,感觉描述不太清楚。

这题的缩点实际上是无向图的类强连通分量,也就是说在无向图中,至少两个点的环才成为类强连通分量。我们需要把图中的类强连通分量,进行缩点。

只需要在 Tarjan 求强连通的过程中加个特判即可,比较神秘。

接着就是求两个点之间的距离,直接用 lca 倍增求。

注意:重构图时注意只需要连单向边,因为原图是无向图,每条边会被遍历到两次。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define MAXN 10005

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

long long n, m, q, dfn[MAXN], low[MAXN], dn, s[MAXN], in[MAXN], lg[MAXN];
long long top, is[MAXN], cnt, scc[MAXN], dep[MAXN], fa[MAXN][30];
vector<long long> v[MAXN], g[MAXN];
bool bt[MAXN];

void tarjan(long long x, long long fa) {
	low[x] = dfn[x] = ++ dn, s[++ top] = x, is[x] = 1;
	for(auto y : v[x]) {
		if(y == fa) continue;
		if(!dfn[y]) tarjan(y, x), low[x] = min(low[x], low[y]);
		else if(is[y]) low[x] = min(low[x], dfn[y]);
	}
	if(dfn[x] == low[x]) {
		cnt ++; 
		do {
			scc[s[top]] = cnt, is[s[top]] = 0;
		} while(s[top --] != x);
	}
	return;
}

void dfs(long long x, long long f) {
	fa[x][0] = f, dep[x] = dep[f] + 1;
	for(int i = 1; i <= lg[dep[x]]; i ++)
		fa[x][i] = fa[fa[x][i - 1]][i - 1];
	for(auto y : g[x]) if(y != f) dfs(y, x);
	return;
}

long long lca(long long x, long long y) {
	if(dep[x] < dep[y]) swap(x, y);
	while(dep[x] > dep[y])
		x = fa[x][lg[dep[x] - dep[y]] - 1];
	if(x == y) return x;
	for(int i = lg[dep[x]] - 1; i >= 0; i --)
		if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
	return fa[x][0];
}

void cg(long long x) {
	long long res = 0;
	while(x) bt[++ res] = x % 2, x /= 2;
	for(int i = res; i >= 1; i --) cout << bt[i];
	cout << "\n"; return;
}

int main() {
	n = read(), m = read();
	for(int i = 1; i <= m; i ++) {
		long long x = read(), y = read();
		v[x].push_back(y), v[y].push_back(x);
	}
	for(int i = 1; i <= n; i ++) if(!dfn[i]) tarjan(i, -1);
	for(int i = 1; i <= n; i ++) for(auto j : v[i]) 
		if(scc[i] != scc[j]) g[scc[i]].push_back(scc[j]); 
	for(int i = 1; i < MAXN; i ++) lg[i] = lg[i >> 1] + 1;
	dfs(1, 0), q = read();
	while(q --) {
		long long x = scc[read()], y = scc[read()];
		cg(dep[x] + dep[y] - 2 * dep[lca(x, y)] + 1);
	}
	return 0;
}

\(\text{luogu-7924}\)

小 A 是一个热衷于旅行的旅行家。有一天,他来到了一个城市,这个城市由 \(n\) 个景点与 \(m\) 条连接这些景点的道路组成。每个景点有一个美观度 \(a_i\)。定义一条旅游路径为两个景点之间的一条非严格简单路径,也就是点可以重复经过,而边不可以。

接下来有 \(q\) 个旅游季,每个旅游季中,小 A 将指定两个顶点 \(x\)\(y\),然后他将走遍 \(x\)\(y\)所有旅游路径。 所有旅游季结束后,小 A 会统计他所经过的所有景点的美观度之和(重复经过一个景点只统计一次美观度)。他希望你告诉他这个美观度之和。

\(3 \leq n \leq 5 \times 10^5\)\(m \leq 2 \times 10^6\)\(q\le10^6\)\(1 \leq a_i \leq 100\),且该图联通,没有重边和自环。


非常毒瘤的一道题,指的是数据。

实际上思路并不难,显然若能到达边双其中的一个点,则这个边双所有点都可达。

于是考虑把边双缩成点,这样图就变成了一颗树。路径 \(x \to y\) 也就是从 \(x\) 所在的边双到 \(y\) 所在的边双,只能走最短路径。需要特判 \(x,y\) 在同一个边双里的情况。

路径上经过的点都是可达点,由于每个点不能重复计算贡献,可以用树上差分解决。

于是这道题就做完了,但是数据卡常,不放代码了,因为我不想卡了。

posted @ 2026-01-13 20:55  So_noSlack  阅读(3)  评论(0)    收藏  举报