Codeforces Round #805 (Div. 3) G2. Passable Paths (hard version)(树上LCA)

原题链接:Problem - G2 - Codeforces

题目大意: 

        给出一棵树,给出一个点的集合,问是否能通过一条简单路径(即在树上找一条路径且不重复走一条边)连接所有点。

解题思路: 

        这题我第一眼想到类似树链剖分的东西,跑一次 bfs 求出点集中深度最大的点,然后以这个点为根再跑一次bfs(类似求树的直径),再分出一堆儿子,再从每儿子的叶子用记忆化dfs回溯一下路径,遇到一个集合中的点就 cnt + 1 直到cnt == 集合大小 时就说明存在路径。

        但是这样只能过easy 因为跑一次 bfs 就是 2e5 的复杂度,然后又有 2e5 次询问。显然T飞了。

        这样我们考虑另一个方法,因为这题是树。看出可能可以用 LCA ,那么往 LCA 方面想。

        分类讨论,如果给出的是 { 1,3,5 } 即如图:

        那么显然,如果都在一条链上,我们是可以很容易求的,这里我们可以用dfs序,判断是否深度大的点是否在深度小的点的子树内部。如果对任意点都满足,那么说明就存在这种链式的情况。 我们只需要按照深度递减排序一下点集,然后 for 一遍从大往小判断子树就行了。

	void dfsorder(int u, int fa)//dfs序
{
	in[u] = ++t;
	for (auto& x : g[u])
		if (x != fa) dfsorder(x, u);
	out[u] = ++t;
}

bool intree(int a, int b)//判断是否在子树
{
	return in[a] <= in[b] && out[b] <= out[a];
}

bool isline(vector<int>& arr)//判断是否是一条链
{
	for (int i = arr.size() - 1; i - 1 >= 0; --i)
		if (!intree(arr[i], arr[i - 1])) return false;
	return true;
}

void solve()
{
    vector<int> arr(m);//存点集
	for (auto& x : arr) cin >> x;

	ranges::sort(arr, [&](int x, int y) {return d[x] > d[y]; });//排序深度递减
	if (isline(arr)) { cout << "YES\n"; continue; };//判断链
}

        那么还有不是链式的情况,例如 { 3,4,5 }, 如图:

         

         这种情况,我们还是可以看出,虽然他们不在一条链上,但是如果都能通过简单路径来相连的话。说明这个点集合 {3,4,5} 必定是某个节点的子树的子集。也就是图中的点 2 ,我们暂且称之为 top 那么,我们只需要判断有没有这个 top 满足条件就行了。

        很容易想到 LCA ,要正确求得这个 top ,首先我们不能在同一个子树求 LCA。这个判断很简单 对于 4 和 5 来说 4 的深度比 5 小, 而4 和 5的 LCA 为 4。也就是说,我们不能让两个点的LCA 等于两点中深度小的。只要满足两点 LCA(a, b\neq a or b 就可以了。

        其次是我们要一直选深度大的点 和 剩下的点来枚举,枚举顺序从深度大向深度小。

        也就是对于 { 3,4,5 }  我们要定下深度最大的 5,然后从4 -> 3这样深度递减或相等的顺序枚举,这样得到的 top 才是正确的。我们记深度最大的点为 st 即路径起点,而另一个枚举的满足 LCA(a, b\neq a or b 的点为 ed ,即路径终点,那么 top 就是 LCA(st, ed) 了

void solve()
{
    int m; cin >> m;
    vector<int> arr(m);//存点集
	for (auto& x : arr) cin >> x;

	ranges::sort(arr, [&](int x, int y) {return d[x] > d[y]; });//排序深度递减
	if (isline(arr)) { cout << "YES\n"; continue; };//判断链

	int st = arr[0], ed;
	for (auto& x : arr)
		if (lca(st, x) != x)//找到另一个点
		{
			ed = x;
			break;
		}
    int top = lca(st, ed);//求得top
}

        剩下的就是判断路径是否合法了。 对于点集里的点可以想到:

        存在一个 top ,说明 st 和 ed 都分别在两边。对于剩下的点(x)我们只需要考虑:

        1. 该点(x)是否在 st 到 top 的路径上。

        2. 该点(x)是否在 ed 到 top 的路径上。

        对于条件1:可以转化为 lca(st,x) = x, lca(ed, x) = top

        对于条件2:可以转化为 lca(ed,x) = x, lca(st,x) = top

        如果都不满足这两个条件,说明该点(x)既不在st -> top的路径,也不在ed -> top的路径。

void solve()
{
		for (auto& x : arr)//枚举剩下的点x
			if (lca(st, x) == x && lca(ed, x) == top)continue;//条件1
			else if (lca(ed, x) == x && lca(st, x) == top) continue;//条件2
			else
			{
				ok = false;//否则
				break;
			}
		cout << (ok ? "YES\n" : "NO\n");
}

以上,这题就解决了。下面是完整代码。

AC代码: 

#include <bits/stdc++.h>
using namespace std;

using i64 = long long;
const int N = 2e5 + 10;

int d[N], pre[N][21];//lca相关 d为深度
int in[N], out[N], t;//dfs序相关
vector<int> g[N];//vector存图

inline void getlca(int u, int fa)//lca板子
{
	d[u] = d[fa] + 1, pre[u][0] = fa;
	for (int i = 1; (1LL << i) <= d[u]; ++i)
		pre[u][i] = pre[pre[u][i - 1]][i - 1];
	for (auto& x : g[u])
		if (!d[x]) getlca(x, u);
}

int lca(int a, int b)//lca板子
{
	if (d[a] > d[b]) swap(a, b);
	for (int i = 20; ~i; --i)
		if (d[a] <= d[b] - (1LL << i)) b = pre[b][i];
	if (a == b) return a;
	for (int i = 20; ~i; --i)
		if (pre[a][i] != pre[b][i])
			a = pre[a][i], b = pre[b][i];
	return pre[a][0];
}

void dfsorder(int u, int fa)//dfs序
{
	in[u] = ++t;
	for (auto& x : g[u])
		if (x != fa) dfsorder(x, u);
	out[u] = ++t;
}

bool intree(int a, int b)//判断是否在子树
{
	return in[a] <= in[b] && out[b] <= out[a];
}

bool isline(vector<int>& arr)//判断是否是一条链
{
	for (int i = arr.size() - 1; i - 1 >= 0; --i)
		if (!intree(arr[i], arr[i - 1])) return false;
	return true;
}

void solve()
{
	int n, q; cin >> n;
	for (int i = 1, x, y; i <= n - 1; ++i)
	{
		cin >> x >> y;
		g[x].emplace_back(y);
		g[y].emplace_back(x);
	}

	getlca(1, 0);
	dfsorder(1, 0);

	cin >> q;
	for (int i = 0; i < q; ++i)
	{
		bool ok = true;
		int m; cin >> m;
		vector<int> arr(m);//存点集
		for (auto& x : arr) cin >> x;

		ranges::sort(arr, [&](int x, int y) {return d[x] > d[y]; });//排序深度递减
		if (isline(arr)) { cout << "YES\n"; continue; };//判断链

		int st = arr[0], ed;
		for (auto& x : arr)
			if (lca(st, x) != x)//找到另一个点
			{
				ed = x;
				break;
			}

		int top = lca(st, ed);
		for (auto& x : arr)
			if (lca(st, x) == x && lca(ed, x) == top)continue;
			else if (lca(ed, x) == x && lca(st, x) == top) continue;
			else
			{
				ok = false;
				break;
			}
		cout << (ok ? "YES\n" : "NO\n");
	}
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);

	int t = 1;
	//cin >> t;
	while (t--) solve();

	return 0;
}

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

柠檬味的橙汁

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

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

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

打赏作者

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

抵扣说明:

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

余额充值