边双连通分量
\(\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\) 在同一个边双里的情况。
路径上经过的点都是可达点,由于每个点不能重复计算贡献,可以用树上差分解决。
于是这道题就做完了,但是数据卡常,不放代码了,因为我不想卡了。
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/19479208

浙公网安备 33010602011771号