G2. Passable Paths (hard version) (树的直径 + lca)

Codeforces Round #805 (Div. 3)

Problem

给出一个n个结点的树。现在有q个查询,每个查询给出k个点,问:是否存在一条简单路径,使得,这k个点都在这条路径上。路径上可以经过这k个点以外的其他点。但要求每条边最多只能走一次。

Solution

  1. 以1为根节点的有根树,树上的简单路径无非两种:(1)从上到下(2)从下到上再到下
  2. 本题类似于“树的直径”,先讨论如何求树的直径:
    先找到距离根节点最远的一个结点,这个结点就是直径的两个端点之一;
    然后再找到距离这个点最远的一个点,这个点就是直径的另一个端点。
  3. 找到这条简单路径的两个端点:
    先找出k个点中距离根节点最远的一个点,这个点一定是这条简单路径上的一个端点(记这个点为 l l l )。
    寻找另一个端点(记为 r r r ):k个点中距离 点 l l l 最远的一个点就是 r r r
  4. 判断k个点是否都在这条路径上:
    在这条路径上的点,到两端的距离和都等于点 l l l r r r 的距离。
    不在这条路径上的点,因为要走重边,所以到两端的距离和大于两个端点之间的距离。

Code

const int N = 2e5 + 5, M = 1e6 + 7;
int a[N], b[N];
vector<int> v[N];

int d[N],dis[N], f[N][20], t;
void bfs()
{
	queue<int> q;
	q.push(1);
	d[1] = 1;
	while (sz(q))
	{
		int x = q.front(); q.pop();
		for (int y : v[x])
		{
			if (d[y]) continue;
			d[y] = d[x] + 1;
			f[y][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]) return lca(y, x);
	for (int i = t; i >= 0; i--)
		if (d[f[y][i]] >= d[x]) y = f[y][i];
	if (x == y) return x;
	for (int i = t; i >= 0; i--)
		if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	return f[x][0];
}

int main()
{
	IOS;
	int n; cin >> n;

	t = log(n) / log(2) + 1;
	for (int i = 1; i < n; i++)
	{
		int x, y; cin >> x >> y;
		v[x].push_back(y); v[y].push_back(x);
	}

	bfs();
	int q; cin >> q;
	while (q--)
	{
		int k; cin >> k;
		int l = 0, r = 0;
		for (int i = 1; i <= k; i++)
		{
			cin >> a[i];
			if (d[a[i]] > d[l]) l = a[i];
		}

		if (k <= 2)
		{
			cout << "YES\n";
			continue;
		}

		int ma = 0;
		for (int i = 1; i <= k; i++)
			if (d[l] + d[a[i]] - d[lca(l, a[i])] * 2 > ma)
				ma = d[l] + d[a[i]] - 2 * d[lca(l, a[i])],
				r = a[i];
		
		int flag = 1;
		for (int i = 1; i <= k; i++)
			if (d[a[i]] + d[l] - d[lca(a[i], l)] * 2 + d[r] + d[a[i]] - d[lca(a[i], r)] * 2 > ma)
				flag = 0;

		if (flag) cout << "YES\n";
		else cout << "NO\n";
	}


	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

to cling

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

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

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

打赏作者

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

抵扣说明:

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

余额充值