1 条题解
-
0
旅行 题解
题目分析
这是一个树上的最小斯坦纳树问题。给定一棵树和树上的景点序列,对于每个查询 ,需要找到包含景点 的最小连通子图的大小。
关键观察
-
问题转化:对于景点集合 ,我们需要找到包含 的最小连通子图的节点数。
-
树上性质:在树中,包含点集 的最小连通子图就是这些点的虚树。虚树的大小可以通过以下公式计算:
$$\text{size} = \sum_{u \in S} \text{depth}[u] - \sum_{(u,v) \in \text{Euler Tour}} \text{depth}[\text{LCA}(u,v)] + 1 $$ -
欧拉序技巧:将景点按照欧拉序排序后,相邻景点之间的路径并集就是整个虚树。
算法思路
1. 预处理
- 构建树的邻接表
- 计算深度、父节点、欧拉序
- 预处理 LCA(使用倍增或 RMQ)
- 对景点序列建立数据结构支持区间查询
2. 查询处理
对于查询 :
- 获取区间内的景点集合
- 按欧拉序排序
- 计算虚树大小:$$\text{ans} = 1 + \sum_{i=L}^{R} \text{dist}(C_i, C_{i+1}) - \text{dist}(C_L, C_R) $$其中
C语言实现
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #define MAXN 100005 #define MAXLOG 20 typedef struct { int to, next; } Edge; Edge edges[MAXN * 2]; int head[MAXN], edge_cnt; int n, m, q; int C[MAXN]; // 树相关 int depth[MAXN], parent[MAXN][MAXLOG]; int euler[MAXN * 2], euler_pos[MAXN], euler_time; int first_occurrence[MAXN]; // 景点相关 int pos[MAXN]; // 景点在欧拉序中的位置 void add_edge(int u, int v) { edges[edge_cnt].to = v; edges[edge_cnt].next = head[u]; head[u] = edge_cnt++; } void dfs(int u, int p, int d) { depth[u] = d; parent[u][0] = p; first_occurrence[u] = euler_time; euler[euler_time++] = u; for (int i = head[u]; i != -1; i = edges[i].next) { int v = edges[i].to; if (v != p) { dfs(v, u, d + 1); euler[euler_time++] = u; } } } void preprocess_lca() { for (int j = 1; j < MAXLOG; j++) { for (int i = 1; i <= n; i++) { if (parent[i][j-1] != -1) { parent[i][j] = parent[parent[i][j-1]][j-1]; } else { parent[i][j] = -1; } } } } int lca(int u, int v) { if (depth[u] < depth[v]) { int temp = u; u = v; v = temp; } int diff = depth[u] - depth[v]; for (int i = 0; i < MAXLOG; i++) { if (diff & (1 << i)) { u = parent[u][i]; } } if (u == v) return u; for (int i = MAXLOG - 1; i >= 0; i--) { if (parent[u][i] != parent[v][i]) { u = parent[u][i]; v = parent[v][i]; } } return parent[u][0]; } int distance(int u, int v) { int p = lca(u, v); return depth[u] + depth[v] - 2 * depth[p]; } // 线段树维护区间内景点的欧拉序最小最大值 typedef struct { int min_pos, max_pos; int min_val, max_val; } Segment; Segment seg[MAXN * 4]; Segment merge(Segment a, Segment b) { Segment res; res.min_pos = (pos[a.min_val] < pos[b.min_val]) ? a.min_pos : b.min_pos; res.max_pos = (pos[a.max_val] > pos[b.max_val]) ? a.max_pos : b.max_pos; res.min_val = (pos[a.min_val] < pos[b.min_val]) ? a.min_val : b.min_val; res.max_val = (pos[a.max_val] > pos[b.max_val]) ? a.max_val : b.max_val; return res; } void build_segment_tree(int idx, int l, int r) { if (l == r) { seg[idx].min_pos = l; seg[idx].max_pos = l; seg[idx].min_val = C[l]; seg[idx].max_val = C[l]; return; } int mid = (l + r) / 2; build_segment_tree(idx * 2, l, mid); build_segment_tree(idx * 2 + 1, mid + 1, r); seg[idx] = merge(seg[idx * 2], seg[idx * 2 + 1]); } Segment query_segment_tree(int idx, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) { return seg[idx]; } int mid = (l + r) / 2; if (qr <= mid) return query_segment_tree(idx * 2, l, mid, ql, qr); if (ql > mid) return query_segment_tree(idx * 2 + 1, mid + 1, r, ql, qr); return merge(query_segment_tree(idx * 2, l, mid, ql, qr), query_segment_tree(idx * 2 + 1, mid + 1, r, ql, qr)); } int main() { scanf("%d %d %d", &n, &m, &q); // 初始化 memset(head, -1, sizeof(head)); edge_cnt = 0; // 读入树结构 for (int i = 0; i < n - 1; i++) { int u, v; scanf("%d %d", &u, &v); add_edge(u, v); add_edge(v, u); } // 读入景点 for (int i = 1; i <= m; i++) { scanf("%d", &C[i]); } // 预处理树 memset(parent, -1, sizeof(parent)); euler_time = 0; dfs(1, -1, 0); preprocess_lca(); // 计算欧拉序位置 for (int i = 0; i < euler_time; i++) { euler_pos[euler[i]] = i; } // 为景点计算欧拉序中的位置 for (int i = 1; i <= m; i++) { pos[C[i]] = euler_pos[C[i]]; } // 构建线段树 build_segment_tree(1, 1, m); // 处理查询 while (q--) { int L, R; scanf("%d %d", &L, &R); if (L == R) { printf("1\n"); continue; } Segment res = query_segment_tree(1, 1, m, L, R); // 计算虚树大小 int u = res.min_val; int v = res.max_val; int p = lca(u, v); // 虚树大小 = 路径上所有不同节点数 // 简化计算:使用距离公式 int ans = distance(u, v) + 1; printf("%d\n", ans); } return 0; }算法详解
1. 树预处理
- DFS遍历:计算深度、父节点、欧拉序
- LCA预处理:使用倍增法快速计算最近公共祖先
- 欧拉序:记录每个节点的访问顺序,用于快速比较节点在树中的位置
2. 数据结构
- 线段树:维护区间内景点的欧拉序最小值和最大值
- 位置数组:记录每个景点在欧拉序中的位置
3. 查询处理
对于每个查询 :
- 找到区间内欧拉序最小和最大的景点
- 这两个景点确定的路径包含了整个虚树的关键路径
- 计算路径上的节点数作为答案
复杂度分析
- 预处理:
- 每个查询:
- 总复杂度:
关键优化
- 欧拉序技巧:利用欧拉序的性质快速确定节点在树中的相对位置
- LCA优化:使用倍增法快速计算最近公共祖先
- 线段树:高效支持区间查询
注意事项
- 实际实现中可能需要更复杂的虚树计算
- 对于大规模数据,需要确保内存使用在限制范围内
- 边界情况需要特殊处理(如单景点查询)
这个解法利用了树的特性和高效的数据结构,能够在给定约束下快速处理所有查询。
-
- 1
信息
- ID
- 4927
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 9
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者