Tree Queries( Codeforces Round 629 (Div. 3) )
You are given a rooted tree consisting of n n n vertices numbered from 1 1 1 to n n n. The root of the tree is a vertex number 1 1 1.
A tree is a connected undirected graph with n − 1 n-1 n−1 edges.
You are given m m m queries. The i i i-th query consists of the set of k i k_i ki distinct vertices v i [ 1 ] , v i [ 2 ] , … , v i [ k i ] v_i[1], v_i[2], \dots, v_i[k_i] vi[1],vi[2],…,vi[ki]. Your task is to say if there is a path from the root to some vertex u u u such that each of the given k k k vertices is either belongs to this path or has the distance 1 1 1 to some vertex of this path.
Input
The first line of the input contains two integers n n n and m m m ( 2 ≤ n ≤ 2 ⋅ 1 0 5 2 \le n \le 2 \cdot 10^5 2≤n≤2⋅105, 1 ≤ m ≤ 2 ⋅ 1 0 5 1 \le m \le 2 \cdot 10^5 1≤m≤2⋅105) — the number of vertices in the tree and the number of queries.
Each of the next n − 1 n-1 n−1 lines describes an edge of the tree. Edge i i i is denoted by two integers u i u_i ui and v i v_i vi, the labels of vertices it connects ( 1 ≤ u i , v i ≤ n , u i ≠ v i (1 \le u_i, v_i \le n, u_i \ne v_i (1≤ui,vi≤n,ui=vi).
It is guaranteed that the given edges form a tree.
The next m m m lines describe queries. The i i i-th line describes the i i i-th query and starts with the integer k i k_i ki ( 1 ≤ k i ≤ n 1 \le k_i \le n 1≤ki≤n) — the number of vertices in the current query. Then k i k_i ki integers follow: v i [ 1 ] , v i [ 2 ] , … , v i [ k i ] v_i[1], v_i[2], \dots, v_i[k_i] vi[1],vi[2],…,vi[ki] ( 1 ≤ v i [ j ] ≤ n 1 \le v_i[j] \le n 1≤vi[j]≤n), where v i [ j ] v_i[j] vi[j] is the j j j-th vertex of the i i i-th query.
It is guaranteed that all vertices in a single query are distinct.
It is guaranteed that the sum of k i k_i ki does not exceed 2 ⋅ 1 0 5 2 \cdot 10^5 2⋅105 ( ∑ i = 1 m k i ≤ 2 ⋅ 1 0 5 \sum\limits_{i=1}^{m} k_i \le 2 \cdot 10^5 i=1∑mki≤2⋅105).
Output
For each query, print the answer — “YES”, if there is a path from the root to some vertex u u u such that each of the given k k k vertices is either belongs to this path or has the distance 1 1 1 to some vertex of this path and “NO” otherwise.
Example
Input
Copy
10 6
1 2
1 3
1 4
2 5
2 6
3 7
7 8
7 9
9 10
4 3 8 9 10
3 2 4 6
3 2 1 5
3 4 8 2
2 6 10
3 5 4 7
Output
Copy
YES
YES
YES
YES
NO
NO
Note
The picture corresponding to the example:
Consider the queries.
The first query is [ 3 , 8 , 9 , 10 ] [3, 8, 9, 10] [3,8,9,10]. The answer is “YES” as you can choose the path from the root 1 1 1 to the vertex u = 10 u=10 u=10. Then vertices [ 3 , 9 , 10 ] [3, 9, 10] [3,9,10] belong to the path from 1 1 1 to 10 10 10 and the vertex 8 8 8 has distance 1 1 1 to the vertex 7 7 7 which also belongs to this path.
The second query is [ 2 , 4 , 6 ] [2, 4, 6] [2,4,6]. The answer is “YES” as you can choose the path to the vertex u = 2 u=2 u=2. Then the vertex 4 4 4 has distance 1 1 1 to the vertex 1 1 1 which belongs to this path and the vertex 6 6 6 has distance 1 1 1 to the vertex 2 2 2 which belongs to this path.
The third query is [ 2 , 1 , 5 ] [2, 1, 5] [2,1,5]. The answer is “YES” as you can choose the path to the vertex u = 5 u=5 u=5 and all vertices of the query belong to this path.
The fourth query is [ 4 , 8 , 2 ] [4, 8, 2] [4,8,2]. The answer is “YES” as you can choose the path to the vertex u = 9 u=9 u=9 so vertices 2 2 2 and 4 4 4 both have distance 1 1 1 to the vertex 1 1 1 which belongs to this path and the vertex 8 8 8 has distance 1 1 1 to the vertex 7 7 7 which belongs to this path.
The fifth and the sixth queries both have answer “NO” because you cannot choose suitable vertex u u u.
题目大意
本题给定一棵有根树,树中包含 n n n 个从 1 1 1 到 n n n 编号的顶点,根节点为顶点 1 1 1。同时还会给出 m m m 个查询,每个查询包含一组不同的顶点。对于每个查询,需要判断是否存在一条从根节点到某个顶点 u u u 的路径,使得查询中的每个顶点要么属于这条路径,要么与路径上的某个顶点距离为 1 1 1。
输入格式
- 第一行包含两个整数 n n n 和 m m m,分别表示树的顶点数和查询数。
- 接下来的 n − 1 n - 1 n−1 行,每行描述一条树的边,包含两个整数 u i u_i ui 和 v i v_i vi,表示连接的两个顶点。
-
- 再接下来的 m m m 行,每行描述一个查询。每行首先是一个整数 k i k_i ki,表示该查询中的顶点数,随后跟着 k i k_i ki 个不同的整数,表示查询中的顶点。
输出格式
对于每个查询,如果存在满足条件的路径,输出 “YES”,否则输出 “NO”。
解题思路
-
选择最深顶点:在每个查询中,选择一个距离根节点最远的顶点 f v fv fv。因为若存在满足条件的路径,它大概率会延伸到这个最深顶点。
-
顶点替换:将查询中除根节点和 f v fv fv 之外的每个顶点替换为其父节点。这样做是为了将距离路径为 1 1 1 的顶点转化为路径上的顶点来处理。
-
路径判断:检查替换后的每个顶点是否都在从根节点到 f v fv fv 的路径上。为了实现这一判断,使用深度优先搜索(DFS)预处理每个顶点的进入时间
tin
和离开时间tout
。利用这两个时间信息,通过判断区间包含关系来确定一个顶点是否是另一个顶点的父节点,进而判断一个顶点是否在从根节点到 f v fv fv 的路径上。 ### 代码分析
代码整体结构
代码主要由以下几个部分组成:
-
头文件和常量定义:包含了常用的头文件,定义了一些常量,如
MAXN
和LOG
,用于确定最大节点数和倍增算法所需的对数。 -
全局变量:定义了节点数 n n n、查询数 m m m、树的邻接表
tree
、倍增数组up
和深度数组depth
。
#include <map>
#include <set>
#include <fstream>
#include <queue>
#include <deque>
#include <stack>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <iterator>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <cstdio>
#include <bitset>
#include <iomanip>
#define endl '\n'
#define int long long
#define Max(a, b) (((a) > (b)) ? (a) : (b))
#define Min(a, b) (((a) < (b)) ? (a) : (b))
#define BoBoowen ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
using namespace std;
const int inf = 1e9 + 7;
const int N = 5e5 + 10;
const int MAXN = 500005; // 假设节点数最多为 500005
const int LOG = 30; // 2^30 > 500005,足够用于预处理
int n, m; // n 为节点数,m 为查询数
int q;
vector<int> tree[MAXN];
int up[MAXN][LOG], depth[MAXN];
// 深度优先搜索预处理父节点和深度信息
void dfs(int u, int parent, int dep)
{
up[u][0] = parent;
depth[u] = dep;
for (int i = 1; i < LOG; i++)
{
if (up[u][i - 1] != -1)
{
up[u][i] = up[up[u][i - 1]][i - 1];
}
else
{
break;
}
}
for (int v : tree[u])
{
if (v != parent)
{
dfs(v, u, dep + 1);
}
}
}
// 查找 u 和 v 的 LCA
int lca(int u, int v)
{
// 如果 u 比 v 深,交换 u 和 v
if (depth[u] < depth[v])
swap(u, v);
// 将 u 提升到和 v 同样的深度
for (int i = LOG - 1; i >= 0; i--)
{
if (depth[u] - (1 << i) >= depth[v])
{
u = up[u][i];
}
}
// 如果 u 和 v 相同,返回 u
if (u == v)
return u;
// 向上跳跃直到找到 LCA
for (int i = LOG - 1; i >= 0; i--)
{
if (up[u][i] != up[v][i])
{
u = up[u][i];
v = up[v][i];
}
}
// 最终 u 和 v 的父节点即为 LCA
return up[u][0];
}
int cmp(pair<int, int> p1, pair<int, int> p2)
{
return p1.first < p2.first;
}
void solved()
{
// 输入处理
cin >> n >> q;
m = n - 1;
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
tree[u].push_back(v);
tree[v].push_back(u);
}
// 初始化倍增信息
memset(up, -1, sizeof(up));
dfs(1, -1, 0); // 假设 1 为根节点
// 处理查询
while (q--)
{
int k;
cin >> k;
vector<int> v(k);
int depp = 1;
for (int i = 0; i < k; ++i)
{
cin >> v[i];
if (v[i] != 1)
{
v[i] = up[v[i]][0];
}
if (depth[depp] < depth[v[i]])
{
depp = v[i];
}
}
int flag = 1;
for (int i = 0; i < k; ++i)
{
if (lca(v[i], depp) != v[i])
{
flag = 0;
break;
}
}
if (flag)
{
cout << "YES" << endl;
}
else
{
cout << "NO" << endl;
}
}
}
signed main()
{
BoBoowen;
int T = 1;
// cin >> T;
while (T--)
{
solved();
}
}
各部分代码解释
-
dfs
函数 - 接收当前节点u
、父节点parent
和深度dep
作为参数。初始化
up[u][0]
为父节点,depth[u]
为当前深度。-
通过倍增算法预处理
up
数组,用于快速查找祖先节点。 -
递归调用
dfs
函数处理子节点。
-
2.lca
函数 - 首先比较两个节点的深度,将较深的节点提升到与较浅节点相同的深度。 - 然后通过倍增算法向上跳跃,直到找到两个节点的最近公共祖先。
solved
函数 - 读取输入的节点数 n n n 和查询数 q q q,构建树的邻接表。 - 调用dfs
函数进行预处理。 - 对于每个查询,读取查询中的顶点数 k k k 和顶点列表。 - 将非根节点替换为其父节点,并找到最深的顶点depp
。 - 检查每个顶点是否在从根节点到depp
的路径上,通过判断lca(v[i], depp)
是否等于v[i]
来实现。 - 根据检查结果输出 “YES” 或 “NO”。- 主函数 - 初始化输入输出流,调用
solved
函数解决问题。
时间复杂度
整体时间复杂度为 O ( n + m ) O(n + m) O(n+m),其中 n n n 是树的节点数, m m m 是查询数。主要开销在于 DFS 预处理和每个查询的处理。