http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=4097
题意
在一个无向图中,询问u,v,w, v和w是否存在边不相同的路径到u。
题解
点双连通分量:任意两点之间存在两条点互不相同的路径。
边双连通分量:任意两点之间存在两条边互不相同的路径。
点双连通分量也是边双连通分量。
但边双连通分量不是点双连通分量。
一般这种题都需要缩点变树。
用边双连通分量进行缩点,这样只剩下桥,即变成了一颗树,因为两点之间有且只有一条路径可达。
分类讨论:
- v和w在一个连通分量内,如果u也在输出Yes,否则No。
- v和w不在一个连通分量内,那么u必须在v到w的路径之上。
对于条件2,用LCA来判断
- u必须是v的父亲或者是w的父亲。
- 这个父亲不能超过v和w的公共祖先。 即要是LCA(v,w)的儿子。
用边双连通分量缩点要注意桥是双向的,加边的话只要取出一个桥加边即可,不然会加两倍的重复边。
代码
#include <bits/stdc++.h>
using namespace std;
#define FOR0(a,b) for(int i = a; i < b; ++i)
#define FORE(a,b) for(int i = a; i <= b; ++i)
typedef long long ll;
typedef pair<int,int> pii;
const int maxn = 1e5+5;
const int maxm = 2e5+5;
vector<int> G[maxn];
vector<pii> edges;
vector<int> G2[maxn];
int n,m,q, dfn[maxn], low[maxn], belong[maxn], isbridge[2*maxm];
int conn[maxn];
int bcc_cnt, dfn_tim;
inline int read()
{
int k=0;
char f=1;
char c=getchar();
for(;!isdigit(c);c=getchar() )
if(c=='-')
f=-1;
for(;isdigit(c);c=getchar() )
k=k*10+c-'0';
return k*f;
}
int find(int x) {
return x == conn[x] ? x : conn[x] = find(conn[x]);
}
void tarjan(int u, int fa) {
dfn[u] = low[u] = ++dfn_tim;
// cout << u <<" "<< dfn[u] << endl;
for(int i = 0; i < G[u].size(); ++i) {
int v = edges[G[u][i]].second;
if(!dfn[v]) {
tarjan(v,u);
low[u] = min(low[u], low[v]);
if(low[v] > dfn[u]) {
isbridge[G[u][i]] = isbridge[G[u][i]^1] = 1;
}
} else if(dfn[v] < dfn[u] && v != fa) {
low[u] = min(low[u], dfn[v]);
}
}
}
void dfs(int u) {
dfn[u] = 1;
belong[u] = bcc_cnt;
for(int i = 0; i < G[u].size(); ++i) {
int e = G[u][i];
int v = edges[e].second;
if(!isbridge[e] && !dfn[v]) {
dfs(v);
}
}
}
void find_ebcc() {
dfn_tim = bcc_cnt = 0;
memset(dfn, 0, sizeof dfn);
memset(low, 0, sizeof low);
memset(isbridge,0,sizeof isbridge);
for(int i = 1; i <= n; ++i) {
if(!dfn[i])
tarjan(i,-1);
}
memset(dfn, 0, sizeof dfn);
for(int i = 1; i <= n; ++i) {
if(!dfn[i]) {
bcc_cnt++;
dfs(i);
}
}
}
void add(int u, int v) {
edges.push_back(pii(u,v));
edges.push_back(pii(v,u));
G[u].push_back(edges.size()-2);
G[v].push_back(edges.size()-1);
}
int p[maxn][18], dep[maxn];
void dfs2(int u, int fa) {
for(int i = 1; i <= 17; ++i) {
p[u][i] = p[p[u][i-1]][i-1];
}
for(int i = 0; i < G2[u].size(); ++i) {
int v = G2[u][i];
if(v != fa) {
dep[v] = dep[u]+1;
p[v][0] = u;
dfs2(v,u);
}
}
}
int lca(int x, int y) {
if(dep[x] < dep[y])
swap(x,y);
for(int i = 17; i >= 0; --i) {
if(dep[p[x][i]] >= dep[y])
x = p[x][i];
}
if(x == y) return x;
for(int i = 17; i >= 0; --i) {
if(p[x][i] != p[y][i]) {
x = p[x][i];
y = p[y][i];
}
}
return p[x][0];
}
void solve() {
find_ebcc();
for(int i = 0; i < edges.size(); i += 2) {
int u = edges[i].first, v = edges[i].second;
if(isbridge[i]) {
G2[belong[u]].push_back(belong[v]);
G2[belong[v]].push_back(belong[u]);
}
u = find(u); v = find(v);
if(u != v)
conn[u] = v;
}
for(int i = 1; i <= bcc_cnt; ++i) {
if(!dep[i]) {
dep[i] = 1;
dfs2(i,-1);
}
}
}
int main() {
int T;
scanf("%d",&T);
while(T--) {
scanf("%d%d%d", &n, &m, &q);
memset(p,0,sizeof p);
memset(dep,0,sizeof dep);
for(int i = 1; i <= n; ++i) {
p[i][0] = 0;
G[i].clear();
G2[i].clear();
conn[i] = i;
}
edges.clear();
int u,v;
for(int i = 0; i < m; ++i) {
u = read(); v = read();
add(u,v);
}
solve();
for(int i = 0; i < q; ++i) {
int u,v,w;
u = read(); v = read(); w = read();
// scanf("%d%d%d", &u, &v, &w);
if(find(u) != find(v) || find(w) != find(u)) {
puts("No");
continue;
}
// u 不在 v-w的路径上
u = belong[u]; v = belong[v]; w = belong[w];
int uv = lca(u,v);
int vw = lca(v,w);
int uw = lca(u,w);
int uvw = lca(u,vw);
if(v == w) {
if(u == v) puts("Yes");
else puts("No");
} else {
if(uvw == vw && (u == uv || u == uw)) puts("Yes");
else puts("No");
}
}
}
return 0;
}

本文介绍了一个无向图中寻找双连通分量的算法,用于判断两个节点是否存在边不相同的路径到达第三个节点。通过点双连通分量和边双连通分量的概念,使用缩点变树的方法,结合LCA算法进行路径判断。
185

被折叠的 条评论
为什么被折叠?



