【ZOJ 4097 & The 19th Zhejiang University Programming Contest H】Rescue the Princess【边双连通缩点+LCA】

本文探讨了在无向图中寻找两条不相交路径的问题,详细解析了边双连通分量的概念及其在解决此类问题中的应用。通过缩点和树上的LCA算法,文章提供了一种高效解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

给出一个无向图,nnn个点,mmm条边,可能有重边与自环,也可能不连通。qqq 组询问,每组询问给出333个点,uuuvvvwww,问是否存在两条路径不存在公共边,并且一条路径是v→uv\rightarrow uvu,另一条路径是w→uw\rightarrow uwu,存在输出YesYesYes,否则输出NoNoNo. (1≤n≤105,0≤m≤2∗105,1≤q≤105)(1\leq n \leq 10^5,0\leq m\leq 2*10^5,1\leq q\leq 10^5)(1n105,0m2105,1q105)


思路:

既然是无向图上求两条互不相交的路径,比较直接的想法就是先求出边双连通分量进行缩点,然后在树上进行考虑。

求出边双连通分量之后,假如 uuuvvvwww 三点在同一个双连通分量中,则答案必定为YesYesYes。若 vvvwww 在同一个双连通分量中,而uuu在另一个双连通分量中,则答案必定为NoNoNo。若vvvwwwuuu在同一个双连通分量中,另一个点不在其中,则答案也为YesYesYes。考虑完了一个和两个双连通分量的情况之后,我们来考虑三个的情况。

假如 uuuvvvwww 分属于三个不同的双连通分量中,则需要进行分类。YesYesYes 的情况只有两种,第一种情况是vvvwww都在uuu子树中,即lca(v,w)=ulca(v,w) = ulca(v,w)=u即可,如图(1)(1)(1)。第二种情况是vvvwww中有一个在uuu的子树中,另一个则不在。对于这种情况,我们先求出lca(v,w)=ylca(v,w) = ylca(v,w)=y,再求出x1=lca(u,w),x2=lca(u,v)x_1 = lca(u,w),x_2=lca(u,v)x1=lca(u,w),x2=lca(u,v),则x1x_1x1x2x_2x2中一定有一个为yyy,另一个为uuu,才能输出YesYesYes,否则输出NoNoNo. 到此,这题分类讨论就结束了。
在这里插入图片描述
但是这一题还需要注意一些细节,因为图可能不连通,因此需要预先判断uuuvvvwww三个点是否连通,如果不连通,直接输出NoNoNo. 还有一个细节,因为图可能是个森林,因此需要对每一个树进行 lcalcalca 处理。


反思:

比赛的时候想法的确是正确的,也考虑到了图不连通这一特殊情况。但是忘记了图不连通时,需要对森林中每一颗树预处理一遍 lcalcalca,导致最终也没有通过此题,实力还是非常有待提升,继续加油!!


代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#include <queue>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 2*1e5+100;
const int M = 5*1e5+100;
const db EPS = 1e-9;
using namespace std;

struct Edge{	
	int to,next;
}e[M],ec[M];
int n,m,q,head[N],dfn[N],low[N],headc[N],dis[N],d[N],f[N][20],t;
bool bridge[M];
int tot,num,dcc,tc,DD[N];
int c[N];

void init(){
	tot = tc = 1;
	num = dcc = 0;
	rep(i,0,n) head[i] = headc[i] = 0;
	rep(i,0,2*m) bridge[i] = 0;
	rep(i,0,n) c[i] = dfn[i] = low[i] = 0;
	rep(i,0,n) dis[i] = d[i] = DD[i] = 0;
}

void add(int x,int y){
	e[++tot].to = y; e[tot].next = head[x]; head[x] = tot; 
}

void addc(int x,int y)
{
	ec[++tc].to = y; ec[tc].next = headc[x]; headc[x] = tc;
}

void tarjan(int x,int in_edge)
{
	dfn[x] = low[x] = ++num;
	for(int i = head[x]; i ; i = e[i].next)
	{
		int y = e[i].to;
		if(!dfn[y]){
			tarjan(y,i);  //传入边(x,y)的序号
			low[x] = min(low[x],low[y]);
			if(low[y] > low[x])  //该边连接(x,y),y点无法连接x点上面的点,因此该边是割边
				bridge[i] = bridge[i^1] = true;
		}
		else if(i != (in_edge^1)) //y点所连接的边 不能是(x,y)边的反向边
			low[x] = min(low[x],dfn[y]);
	}
}

void dfsD(int x,int hp){
	DD[x] = hp;
	for(int i = head[x]; i; i = e[i].next){
		int y = e[i].to;
		if(DD[y] == 0) dfsD(y,hp);
	}
}

void dfs(int x)	//用于将图划分为多个边双连通分量
{
	c[x] = dcc;
	for(int i = head[x]; i ; i = e[i].next)
	{
		int y = e[i].to;
		if(c[y] || bridge[i]) continue;  //如果点y已经属于别的强连通分量,或者边i是割边,则continue
		dfs(y);
	}
}

void bfs(int s)
{
	queue<int> q;
	while(q.size()) q.pop();
	q.push(s); d[s] = 1; dis[s] = 0;	//把1当做树根
	while(q.size())
	{
		int x = q.front(); q.pop();
		for(int i = headc[x]; i ;i = ec[i].next){
			int y = ec[i].to;
			if(d[y]) continue;
			d[y] = d[x]+1;
			dis[y] = dis[x]+1;	//dist[y]:从1到y的距离
			f[y][0] = x;  //y走2^0步到达x
			for(int j = 1; j <= t;j++)
				f[y][j] = f[f[y][j-1]][j-1];
			q.push(y);
		}
	}
}

int lca(int x,int y)
{
	if(d[x] > d[y]) swap(x,y);
	for(int i = t; i >= 0; i--)
		if(d[f[y][i]] >= d[x]) y = f[y][i];  //往上追溯,直至y和x位于同一深度
	if(x == y) return x;  //如果已经找到了,就返回x
	for(int i = t; i >= 0; i--)
		if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];  //x和y同时往上走,一直到x和y恰好为lca的子节点
	return f[x][0];  //x和y共同的根节点就是lca 
}


int main()
{
	int _; scanf("%d",&_);
	while(_--){
		scanf("%d%d%d",&n,&m,&q);
		init();
		rep(i,1,m){
			int xx,yy; scanf("%d%d",&xx,&yy);
			add(xx,yy); add(yy,xx);
		}
		int ctt = 0;
		rep(i,1,n)
			if(!DD[i]) dfsD(i,++ctt);
		rep(i,1,n)
			if(!dfn[i]) tarjan(i,0); //将边序号传进去
		rep(i,1,n)
			if(!c[i]){ //如果i点未被标记过
				++dcc;
				dfs(i);
			}
		rep(i,2,tot){
			int x = e[i^1].to, y = e[i].to; //记录该边连接的两个端点
			if(c[x] == c[y]) continue; //如果连接的两个点属于同一连通分量,则continue
			addc(c[x],c[y]);  //用连通的分量的编号来代表整个连通分量,以此来进行缩点
			addc(c[y],c[x]);
		}
		t = (int)(log(dcc+1)/log(2))+1;
		rep(i,1,dcc)
			if(d[i] == 0) bfs(i);
		rep(i,1,q){
			int u,v,w; scanf("%d%d%d",&u,&v,&w);
			if(DD[u] == DD[v] && DD[u] == DD[w]){
				if(c[u] == c[v] && c[u] == c[w]) printf("Yes\n");
				else if(c[v] == c[w] && c[v] != c[u]) printf("No\n");
				else if(c[u] != c[v] && c[u] != c[w] && c[v] != c[w]){
					int y = lca(c[v],c[w]);
					if(y == c[u]) printf("Yes\n");
					else{
						int x1 = lca(c[v],c[u]);
						int x2 = lca(c[w],c[u]);
						if(x1 == y && x2 == c[u]) printf("Yes\n");
						else if(x2 == y && x1 == c[u]) printf("Yes\n");
						else printf("No\n");
					}
				}
				else if(c[v] == c[u] && c[w] != c[v]) printf("Yes\n");
				else if(c[w] == c[u] && c[w] != c[v]) printf("Yes\n");
				else printf("No\n");
			}
			else printf("No\n");
		}
	}
	return 0;
}

由于ZOJ不能交题了,再次给出数据生成代码,以供对拍。(ZOJ现在可以交题了,题目可以在Problem里面找到)

#include <iostream>
#include <cstdlib>
#include <ctime>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;

char tmp[10] = {'I','S','F','A'};
int vis[1000];

int main()
{
    freopen("/Users/gene_liu/Desktop/pai/text.txt","w",stdout);
    srand(time(0));
    int n = 1e5, m = 2*1e5, q = 1e5;
	printf("1\n%d %d %d\n",n,m,q);
	rep(i,1,m){
		int x = rand()%n+1, y = rand()%n+1;
		printf("%d %d\n",x,y);
	}
	rep(i,1,q){
		int u = rand()%n+1, v = rand()%n+1, w = rand()%n+1;
		printf("%d %d %d\n",u,v,w);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gene_INNOCENT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值