小蓝准备去星际旅行,出发前想在本星系采购一些零食,星系内有 nnn 颗星球,由 n−1n − 1n−1 条航路连接为连通图,第 iii 颗星球卖第 cic_ici 种零食特产。小蓝想出了 qqq 个采购方案,第 iii 个方案的起点为星球 sis_isi ,终点为星球 tit_iti ,对于每种采购方案,小蓝将从起点走最短的航路到终点,并且可以购买所有经过的星球上的零食(包括起点终点),请计算每种采购方案最多能买多少种不同的零食。
解决思路:
- 树的性质:
- 星系中的星球和航路构成了一棵树,树中任意两个节点之间有且仅有一条路径。
- 最短路径就是树中两个节点之间的唯一路径。
- 核心问题:
- 对于每个查询 (s,t)(s, t)(s,t),需要找到从 sss 到 ttt 的路径,并统计路径上所有节点的零食种类数。
- 关键技术:
- 最近公共祖先(LCA):用于快速找到两个节点的最近公共祖先,从而确定路径。
- 路径遍历:从 sss 和 ttt 分别向上遍历到 LCA,收集路径上的零食种类。
- 集合去重:使用集合(如
std::unordered_set
)存储零食种类,确保统计结果不重复。
实现步骤:
要解决这个问题,我们可以使用最近公共祖先(LCA)和路径查询的方法。具体步骤如下:
-
树的表示:由于星系内的星球和航路构成了一棵连通树,我们可以用邻接表来表示这棵树。
-
预处理:
- 使用深度优先搜索(DFS)或广度优先搜索(BFS)来预处理每个节点的深度和父节点。
- 使用二进制提升法(Binary Lifting)来预处理每个节点的祖先信息,以便快速查询LCA。
-
路径查询:
- 对于每个查询
(s, t)
,首先找到它们的LCA。 - 然后从
s
到 LCA 和从t
到 LCA 的路径上收集所有不同的零食种类。
- 对于每个查询
-
统计不同零食种类:
- 使用一个集合(如
std::unordered_set
)来存储路径上的零食种类,最后返回集合的大小。
- 使用一个集合(如
以下是C++代码实现:
#include <iostream>
#include <vector>
#include <unordered_set>
#include <queue>
#include <cmath>
using namespace std;
const int MAXN = 1e5 + 5;
const int LOG = 20;
vector<int> adj[MAXN];
int depth[MAXN];
int up[MAXN][LOG];
int c[MAXN];
void dfs(int v, int p) {
up[v][0] = p;
for (int i = 1; i < LOG; ++i) {
up[v][i] = up[up[v][i - 1]][i - 1];
}
for (int u : adj[v]) {
if (u != p) {
depth[u] = depth[v] + 1;
dfs(u, v);
}
}
}
int lca(int u, int v) {
if (depth[u] < depth[v]) swap(u, v);
for (int i = LOG - 1; i >= 0; --i) {
if (depth[u] - (1 << i) >= depth[v]) {
u = up[u][i];
}
}
if (u == v) return u;
for (int i = LOG - 1; i >= 0; --i) {
if (up[u][i] != up[v][i]) {
u = up[u][i];
v = up[v][i];
}
}
return up[u][0];
}
unordered_set<int> getPath(int u, int v) {
int ancestor = lca(u, v);
unordered_set<int> snacks;
while (u != ancestor) {
snacks.insert(c[u]);
u = up[u][0];
}
while (v != ancestor) {
snacks.insert(c[v]);
v = up[v][0];
}
snacks.insert(c[ancestor]);
return snacks;
}
int main() {
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; ++i) {
cin >> c[i];
}
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
dfs(1, 1);
while (q--) {
int s, t;
cin >> s >> t;
unordered_set<int> snacks = getPath(s, t);
cout << snacks.size() << endl;
}
return 0;
}
代码说明:
- DFS预处理:
dfs
函数用于预处理每个节点的深度和祖先信息。 - LCA查询:
lca
函数用于查询两个节点的最近公共祖先。 - 路径查询:
getPath
函数用于获取从u
到v
的路径上的所有零食种类。 - 主函数:读取输入数据,构建树,处理查询,并输出结果。
复杂度分析:
- 预处理:
- DFS 遍历:O(n)O(n)O(n)。
- 二进制提升预处理:O(n log n)O(n\,log\,n)O(nlogn)。
- 查询:
- LCA 查询:O(log n)O(log\,n)O(logn)。
- 路径遍历:O(k)O(k)O(k),其中 kkk 是路径长度。
- 总查询复杂度:O(q∗(logn+k))O(q * (log n + k))O(q∗(logn+k))。