原题链接:Problem - G2 - Codeforces
题目大意:
给出一棵树,给出一个点的集合,问是否能通过一条简单路径(即在树上找一条路径且不重复走一条边)连接所有点。
解题思路:
这题我第一眼想到类似树链剖分的东西,跑一次 bfs 求出点集中深度最大的点,然后以这个点为根再跑一次bfs(类似求树的直径),再分出一堆儿子,再从每儿子的叶子用记忆化dfs回溯一下路径,遇到一个集合中的点就 cnt + 1 直到cnt == 集合大小 时就说明存在路径。
但是这样只能过easy 因为跑一次 bfs 就是 2e5 的复杂度,然后又有 2e5 次询问。显然T飞了。
这样我们考虑另一个方法,因为这题是树。看出可能可以用 LCA ,那么往 LCA 方面想。
分类讨论,如果给出的是 { } 即如图:
那么显然,如果都在一条链上,我们是可以很容易求的,这里我们可以用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; };//判断链
}
那么还有不是链式的情况,例如 { }, 如图:
这种情况,我们还是可以看出,虽然他们不在一条链上,但是如果都能通过简单路径来相连的话。说明这个点集合 {} 必定是某个节点的子树的子集。也就是图中的点 ,我们暂且称之为 top 那么,我们只需要判断有没有这个 top 满足条件就行了。
很容易想到 LCA ,要正确求得这个 top ,首先我们不能在同一个子树求 LCA。这个判断很简单 对于 和 来说 的深度比 小, 而 和 的 LCA 为 。也就是说,我们不能让两个点的LCA 等于两点中深度小的。只要满足两点 LCA(, ) 就可以了。
其次是我们要一直选深度大的点 和 剩下的点来枚举,枚举顺序从深度大向深度小。
也就是对于 { } 我们要定下深度最大的 ,然后从4 -> 3这样深度递减或相等的顺序枚举,这样得到的 top 才是正确的。我们记深度最大的点为 st 即路径起点,而另一个枚举的满足 LCA(, ) 的点为 ed ,即路径终点,那么 top 就是 LCA(, ) 了。
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:可以转化为
对于条件2:可以转化为
如果都不满足这两个条件,说明该点(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;
}