题目链接
题目大意:
给你一个图(不保证连通,可能存在重边),多次查询,一次查询给出三个点u,v,w,(可能会重合),求有没有两条路径使得v到达u,w到达u,且这两条路径不重合。 点的数量 和 查询次数都是1e5级别。
题解:
先对原图进行边双连通缩点,判断的时候分为以下情况:
1.三个点都在同一个边双连通里,那么肯定存在这样两条不相交路径,想一下边双连通分量的性质就明白了
2.两个起点v,w在同一个边双,而u和它们不是一个边双,肯定不存在解,因为v和w从边双出来一定会经过桥
3.三个点在三个不同的边双里面,把缩点完的图看成一棵树,存在解的条件是:u在v到w的路径上,这个条件可以通过判断(u,v),(u,w),(v,w)三个点对在树上的lca来判断:如果三个lca中深度最大的那个点是u,说明u在v到w的路径上。画一下图就明白了。
AC代码:
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdio>
#define pb push_back
using namespace std;
const int maxn = 1e5 + 50;
inline int read(){
int x = 0;char c = getchar();
while(c<'0'||c>'9') c = getchar();
while(c>='0'&&c<='9') x = (x<<3) + (x<<1) + c - '0',c = getchar();
return x;
}
struct node{
int v,nxt;
}e[maxn<<2];
int cnt = 0;
int head[maxn];
void add(int u,int v){
e[cnt].v = v;
e[cnt].nxt = head[u];
head[u] = cnt++;
}
int dfn[maxn],low[maxn],inq[maxn],fa[maxn],f[maxn][20],bcc[maxn],dep[maxn];
int vis[maxn];
vector<int> g[maxn];
int index = 0,n,m,q;
int fnd(int x){//并查集缩点
if(x == fa[x]) return x;return fa[x] = fnd(fa[x]);
}
void init(){//初始化+建图
cnt = index = 0;
n = read();m = read();q = read();
for(int i = 0;i <= n;++i){
dfn[i] = low[i] = inq[i] = dep[i] = vis[i] = 0;
fa[i] = i;head[i] = -1;
for(int j = 0;j < 20;++j) f[i][j] = 0;
g[i].clear();
bcc[i] = 0;
}
while(m--){
int u = read();
int v = read();
add(u,v);add(v,u);
}
}
void tarjan(int u,int tot,int id){//边双连通缩点
dfn[u] = low[u] = ++index;
inq[u] = 1;
bcc[u] = tot;//标记一块连通图
for(int i = head[u]; ~i ;i = e[i].nxt){
if(i ==(id^1)) continue;
int v = e[i].v;
if(!dfn[v]) {
tarjan(v,tot,i);
low[u] = min(low[u],low[v]);
if(low[v] <= dfn[u]) fa[v] = fnd(u);//low[v] <= dfn[u],说明在同一个边双,缩点
}
else if(inq[v]) low[u] = min(low[u],dfn[v]);
}
inq[u] = 0;
}
void dfs(int u,int de){//给每个结点一个深度,给倍增数组赋值
dep[u] = de;
vis[u] = 1;
for(int j = 0;j < 19;++j) f[u][j+1] = f[f[u][j]][j];
for(int i = 0;i < g[u].size();++i){
int v = g[u][i];
if(!vis[v]) f[v][0] = u,dfs(v,de+1);
}
}
int lca(int u,int v){//倍增求lca
if(dep[u] > dep[v]) swap(u,v);
int d = dep[v] - dep[u];
for(int i = 19;i >= 0;--i) if((1<<i)&d) v = f[v][i];
if(u == v) return u;
for(int i = 19;i >= 0;--i){
if(f[u][i] == f[v][i]) continue;
u = f[u][i];
v = f[v][i];
}
return f[v][0];
}
bool cmp(int u,int v){
return dep[u] > dep[v];
}//定义比较函数
void sol(){
int tot = 0;
for(int i = 1;i <= n;++i) if(!dfn[i]) tarjan(i,++tot,-1);//双连通缩点
for(int i = 0;i < cnt;i+=2){
int u = e[i].v;
int v = e[i+1].v;
u = fnd(u);v = fnd(v);
if(u!=v) g[u].push_back(v),g[v].push_back(u);//如果不在一个双连通,建边
}
for(int i = 1;i <=n;++i) {
int u = fnd(i);
if(!vis[u]) dfs(u,1);//没访问过的连通图,跑一遍
}
while(q--){
int u,v,w;
u = read();v = read();w = read();
if(bcc[u]!=bcc[v] || bcc[u]!=bcc[w]){
printf("No\n");continue;
}//连不起来
u = fnd(u);v = fnd(v);w = fnd(w);
if(u == v|| u == w){//终点和某个起点在同一个双连通
printf("Yes\n");continue;
}
if(v == w){//起点在同一个双连通,但是终点在双连通外面
printf("No\n");continue;
}
vector<int> t;t.clear();
t.pb(lca(u,v));
t.pb(lca(u,w));
t.pb(lca(v,w));
sort(t.begin(),t.end(),cmp);//求出(u,v)(u,w)(v,w)的lca,因为u一定在v到w的路径上,
if(t[0] == u ){ //所以三个lca中深度最大的一定是u
printf("Yes\n");
}
else printf("No\n");
}
}
int main(){
int T;
T = read();
while(T--){
init();sol();
}
}