ZOJ浙大校赛_problem H:Rescue the Princess(图论分析+tarjan边连通分量染色缩点+树上LCA+并查集)

本文深入探讨了一道算法竞赛题目,涉及无向图、桥、边连通分量及树上的路径问题。通过边连通分量缩点转换为树的模型,分析了在树上寻找不共边路径的条件,分享了解题思路与代码实现。

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

题目大意:给出一个无向图(不一定连通),然后给出u,v,w三个点,v 和 w要去u,问是否存在两条路径使得v去u和w去u的路径上不存在任何一条公共边(换句话说一条边只能走一次,问u和v能不能都走到w);

可恶啊,想到了正确的图论模型,可惜只差一点,止步在最后的判断,剩一个小时的时候认为判断太复杂了放弃了(白调了一个小时的代码)。

刚看到可能有点懵,但是一想到怎样才会经过一条边?如果有v,w各自在的地方去u那个点只有一条边连通,那必然要走过同一条边的。很容易想到那样的边其实就是桥吧,再验证一下边连通分量内是否一定都能到,发现在同一个边连通分量内答案是可行的,那么我们可以边连通分量缩点(其实边连通分量缩点和强连通缩点是同一个板子,只有一点细微的区别,这里又学到了一个新的板子,之前用的在书上学的有瑕疵。。)。缩完点重新建图其实就是一棵树嘛,然后变成u,v,两点 如何在树上不经过同一条边走到w点。(在这里分析了可能会走同一条边的情况,结果情况太多了。。。。无语啊)其实只要放过来分析可行的方案就行了,不用分析不可行的方案,反而扰乱判断。三点不重合的情况下唯一可行的方案就是u 在 v,w之间(v,w 不能重合),或者三点都重合也是可行的方案
虽然场上只A了31个人。。但是完全是一道可以做的题。。尽管数据还有很多坑。。
坑点1:(不知道是不是我操作不规范)使用vector 存图会MLE
坑点2: (其实不是坑点。。细心就行) 题目没保证图连通,意味着v,w可能根本就走不到u,这里要用并查集判断一下

在加了并查集和链式向前星存图后,终于A了这道题。。。说起来其实写得很艰难,不过依然是一道可以A的题。。。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
const int maxm = 1e6+10;
const int mx = 19;
int t,n,m,q,x,y;
int vis[maxn],dfn[maxn],belong[maxn],sta[maxn],cnt,res,top,low[maxn];
int grand[maxn][mx + 1],deep[maxn];
struct ss{
 	int v,nxt;
}edg[maxm],ee[maxm];
int head[maxn],hh[maxn],ct,cct;
int p[maxn];
int find(int x){
 	return x == p[x] ? x : p[x] = find(p[x]);
}
void init(){
 	cnt = res = top = 0;
 	memset(dfn,0,sizeof(dfn));
 	memset(low,0,sizeof(low));
 	memset(grand,0,sizeof(grand));
 	memset(head,-1,sizeof(head));
 	memset(hh,-1,sizeof(hh));
 	memset(vis,0,sizeof(vis));
 	memset(deep,0,sizeof(deep));
 	memset(belong,0,sizeof(belong));
 	ct = cct = 0;
}
void add(int u,int v){
 	edg[ct].v = v;
 	edg[ct].nxt = head[u];
 	head[u] = ct++;
}
void ad(int u,int v){
 	ee[cct].v = v;
 	ee[cct].nxt = hh[u];
 	hh[u] = cct++;
}
void tarjan(int s,int fa){
 	low[s] = dfn[s] = ++cnt;
 	sta[++top] = s;
 	bool flag = false;
 	for(int i = head[s]; i + 1; i = edg[i].nxt){
 	 	int v = edg[i].v;
  		if(v == fa && !flag){
   			flag = true;
   			continue;
  		}
  		if(!dfn[v]) tarjan(v,s);
  		low[s] = min(low[s],low[v]);	//规范操作其实是对遍历到访问过的结点去low,对原本没访问过的点取dfn
 						//不过边缩是可以直接取low的,点缩怕是不行,会影响判断(想一想)
 	}
 	if(low[s] == dfn[s]){
  		res++;
  		do{
   			belong[sta[top]] = res;
   			vis[sta[top]] = 0;
  		}while(sta[top--] != s);
 	}
}
void dfs(int s,int fa){
 	for(int i = 1; i < mx; i++)
  		grand[s][i] = grand[grand[s][i - 1]][i - 1];
 	for(int i = hh[s]; i + 1; i = ee[i].nxt){
  		int v = ee[i].v;
  		if(v == fa || deep[v]) continue;
  		grand[v][0] = s;
  		deep[v] = deep[s] + 1;
  		dfs(v,s);
 	}
}
int lca(int u,int v){
 	if(deep[u] < deep[v]) swap(u,v);
 	for(int i = mx; i >= 0; i--){
 		if(deep[grand[u][i]] >= deep[v]){
  			u = grand[u][i];
  		}
 	}
 	for(int i = mx; i >= 0; i--){
  		if(grand[u][i] != grand[v][i]){
   			u = grand[u][i];
   			v = grand[v][i];
  		}
 	}
 	if(u == v) return u;
 	else return grand[u][0];
}
int main(){
 	scanf("%d",&t);
 	while(t--){
 		scanf("%d%d%d",&n,&m,&q);
  		init();
  		for(int i = 1; i <= n; i++) p[i] = i;
  		for(int i = 1; i <= m; i++){
   			scanf("%d%d",&x,&y);
   			if(x == y) continue;
   			add(x,y);
   			add(y,x);
  		}
  		for(int i = 1; i <= n; i++)
   			if(!dfn[i]) tarjan(i,-1);
  		for(int i = 1; i <= n; i++){
   			for(int j = head[i]; j + 1; j = edg[j].nxt){
   				int v = edg[j].v;
    				if(belong[i] != belong[v]){
     					ad(belong[i],belong[v]);
   		  			ad(belong[v],belong[i]);
     					p[find(belong[i])] = p[find(belong[v])];
    				}
   			}
  		}
  		for(int i = 1; i <= res; i++){
  	 		if(!deep[i]){
    				deep[i] = 1;
    				dfs(i,-1);
   			}
  		}
  		for(int i = 1; i <= q; i++){
   			int w,u,v;
   			scanf("%d%d%d",&u,&v,&w);
   			int ui = belong[u];
   			int vi = belong[v];
   			int wi = belong[w];
   			if(find(ui) != find(vi) || find(ui) != find(wi)){
    				puts("No");
    				continue;
   			}
   			int lv = lca(vi,wi);
   			int ans1 = lca(ui,vi);
   			int ans2 = lca(ui,wi);
   			int ans3 = lca(ui,lv);
   			if(vi != wi){
    				if(ans3 == lv && (ans1 == ui || ans2 == ui)) puts("Yes");
    				else puts("No");
   			}
   			else{
    				if(vi == ui) puts("Yes");
    				else puts("No");
   			}
  		}
 	}
 	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值