CF 1702 G2. Passable Paths (hard version) 最近公共祖先(lca)2000

1 篇文章 0 订阅

题意:一颗由 n 个顶点,n - 1 条边组成的树,判断能否一条路走完给出的 k 个顶点。

思路:这 k 个点会有两种情况,一种情况是从一个点只有一条路到底,还有一种情况从一个点分成两条路走下去。我们先从 k 个点中找出两个不在同一条路最深的点 t1 和点 t2。如果只能找到一个点,说明是第一种情况,直接输出 YES。第二种情况:我们求出点 t1 和点 t2 的 lca 点m,那么这 k 个点必须在点 t1 和点 m 之间或者是点 t2 和点 m 之间。如果在点 t1 和点 m 之间, 那么这个点和点 t1 的 lca 就是这个点本身,和点 t2 的 lca 就是点 m,同理可以判断这个点是否在 t2 和 m 之间。如果所有点都满足情况,那么说明这 k 个点在一条路上。

代码

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 2e5 + 10, P = 1e9 + 7, mod = 998244353;
vector<int>a[N];
int dep[N], fa[N][30];
int p[N];
int sz;
void dfs(int x, int f){
	dep[x] = dep[f] + 1;
	fa[x][0] = f;
	for(int i = 1; (1 << i) <= dep[x]; i++)
		fa[x][i] = fa[fa[x][i - 1]][i - 1];
	for(int i = 0; i < a[x].size(); i++)
		if(a[x][i] != f) dfs(a[x][i], x);
}
int lca(int x, int y){
	if(dep[x] < dep[y]) swap(x, y);
	for(int i = sz; i >= 0; i--)
		if(dep[fa[x][i]] >= dep[y]) 
			x = fa[x][i];
	if(x == y) return x;
	for(int i = sz; i >= 0; i--)
		if(fa[x][i] != fa[y][i]) 
			x = fa[x][i], y = fa[y][i];
	return fa[x][0];
}
void solve(){
	int n, q;
	cin >> n;
	sz = 0;
	while((1 << sz) < n) sz++;
	for(int i = 1; i < n; i++){
		int u, v;
		cin >> u >> v;
		a[u].pb(v), a[v].pb(u);
	}
	dfs(1, 0);
	cin >> q;
	while(q--){
		int k; cin >> k;
		for(int i = 1; i <= k; i++) cin >> p[i];
		int t1 = 0, t2 = 0;
		for(int i = 1; i <= k; i++)
			if(dep[p[i]] > dep[t1]) t1 = p[i];
		for(int i = 1; i <= k; i++)
			if(lca(p[i], t1) != p[i] && dep[p[i]] > dep[t2]) 
				t2 = p[i];
		if(!t2){
			cout << "YES" << endl;
			continue;
		}
		int m = lca(t1, t2);
		bool ok = 1;
		for(int i = 1; i <= k; i++){
			int m1 = lca(p[i], t1);
			int m2 = lca(p[i], t2);
			if(!((m1 == p[i] && m2 == m) || (m2 == p[i] && m1 == m)))
				ok = 0;
		}
		if(ok) cout << "YES" << endl;
		else cout << "NO"  << endl;
	}
}                 
int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	//int t;
	//cin >>t;
	//while(t--){
		solve();
	//}
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值