ZOJ-4097 Rescue the Princess(Tarjan边双连通缩点 + LCA)

题目链接
题目大意:
给你一个图(不保证连通,可能存在重边),多次查询,一次查询给出三个点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();
	}
} 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值