This commit is contained in:
Zengtudor 2024-11-16 09:36:34 +08:00
parent 3138c83120
commit 7d0dac0d49
3 changed files with 3 additions and 150 deletions

View File

@ -20,8 +20,6 @@ void fd_ft(const ll &ft, const ll &now){
}
}
int main(){
std::iostream::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);

View File

@ -1,148 +0,0 @@
这个题目要求我们对树的路径进行查询,统计某一路径上不同贝壳种类的数量。每个查询给定一个树上两个节点 \( u \) 和 \( v \),我们需要计算从 \( u \) 到 \( v \) 路径上有多少种不同的贝壳种类。
### 思路
这个问题的核心在于计算路径上的不同元素个数。首先,这是一棵树,树上没有环,因此从任意一个节点到另一个节点的路径是唯一的。我们可以利用树的结构,结合一些优化技巧来高效地解决这个问题。
#### 1. 树的路径表示
从 \( u \) 到 \( v \) 的路径是树上的一条简单路径。我们可以使用 **树的祖先关系** 来表示路径。具体来说,任意一条从节点 \( u \) 到节点 \( v \) 的路径都可以分解为以下两部分:
- 从 \( u \) 到树的根节点(节点 1的路径。
- 从 \( v \) 到树的根节点的路径。
通过找到两个路径的公共祖先 \( LCA(u, v) \)(最近公共祖先),我们就可以通过路径合并来得到从 \( u \) 到 \( v \) 的完整路径。
#### 2. 离线查询和路径压缩
为了高效地回答每个查询,我们可以考虑 **离线查询****路径压缩** 的结合。离线查询的意思是先对所有查询进行排序,然后逐个处理。由于路径查询操作是基于树的结构,我们可以借助 **DFS** 预处理树的深度信息和树的结构。
#### 3. 树的深度和父节点预处理
使用 **深度优先搜索DFS** 遍历树可以构建每个节点的深度、父节点等信息。这些信息将有助于我们高效地计算最近公共祖先LCA
#### 4. 使用哈希表计算不同贝壳种类
在查询路径时,我们可以维护一个哈希表来存储路径上出现的贝壳种类。每次遇到新节点时,将其贝壳种类加入哈希表并进行更新。
### 具体步骤
1. **DFS遍历树**
- 通过DFS计算每个节点的深度。
- 使用 **Euler Tour**欧拉遍历来记录节点在树上的访问顺序便于快速查询LCA。
2. **离线查询:**
- 将所有查询按某种方式排序例如按深度或时间戳排序并结合DFS结果处理每个查询。
3. **LCA计算**
- 可以使用 **二分法提升Binary Lifting** 来优化LCA查询预处理过程中记录每个节点的2^i父节点。
4. **路径上贝壳种类的计算:**
- 在查询时,通过路径压缩技巧,实时更新路径上的贝壳种类。
5. **维护结果:**
- 对每个查询返回路径上不同贝壳种类的数量。
### C++ 代码实现
```cpp
#include <iostream>
#include <vector>
#include <unordered_map>
#include <set>
using namespace std;
const int MAXN = 100000;
vector<int> tree[MAXN];
int c[MAXN]; // 贝壳种类
int depth[MAXN];
int parent[MAXN];
int n, q;
unordered_map<int, int> path_shelld_count; // 存储路径上贝壳种类的出现次数
vector<pair<int, int>> queries[MAXN]; // 存储每个节点的查询
// dfs遍历记录每个节点的深度和父节点
void dfs(int u, int par) {
parent[u] = par;
for (int v : tree[u]) {
if (v != par) {
depth[v] = depth[u] + 1;
dfs(v, u);
}
}
}
// 找LCA
int LCA(int u, int v) {
if (depth[u] < depth[v]) swap(u, v);
// 把u提升到和v一样深
while (depth[u] > depth[v]) u = parent[u];
// 同时提升u和v直到它们相等
while (u != v) {
u = parent[u];
v = parent[v];
}
return u;
}
void process_query(int u, int v) {
set<int> unique_shells; // 用set记录贝壳种类
// 找路径u->root
while (u != -1) {
unique_shells.insert(c[u]);
u = parent[u];
}
// 找路径v->root
while (v != -1) {
unique_shells.insert(c[v]);
v = parent[v];
}
// 返回路径上不同贝壳种类的数量
cout << unique_shells.size() << endl;
}
int main() {
cin >> n >> q;
for (int i = 0; i < n; i++) cin >> c[i]; // 贝壳种类
for (int i = 0; i < n-1; i++) {
int u, v;
cin >> u >> v;
u--; v--;
tree[u].push_back(v);
tree[v].push_back(u);
}
// 处理树
depth[0] = 0;
dfs(0, -1);
// 处理查询
for (int i = 0; i < q; i++) {
int u, v;
cin >> u >> v;
u--; v--; // 转化为0-indexed
process_query(u, v);
}
return 0;
}
```
### 总结
1. **DFS遍历**用来计算每个节点的深度和父节点便于LCA计算。
2. **LCA查询**:通过二分提升方法实现,快速找到两节点的最近公共祖先。
3. **贝壳种类统计**:使用`set`或者`unordered_map`来统计路径上不同的贝壳种类数量。
该算法的复杂度主要依赖于树的深度和LCA查询的优化可以保证在 \( O(n \log n) \) 复杂度下处理完所有查询。
这句话的意思是,给定一棵以节点 `1` 为根的树JJ 想知道从节点 `u` 到节点 `v` 的路径上包含了多少种不同的贝壳种类。树上的路径表示从 `u` 开始,沿着树的边走到 `v` 的路径。
由于题目说明 **“保证 \( u \) 在 \( 1 \sim v \) 的路径上”**,可以解释为以下两点:
1. 节点 \( u \) 一定是在根节点(节点 `1`)到节点 \( v \) 的路径上的某个节点。
2. \( u \) 可以等于 \( v \),即路径可以只包含一个节点。
这个问题要求的是,在给定的路径上,从节点 \( u \) 开始,经过所有的中间节点到达节点 \( v \),这段路径中包含了多少种不同的贝壳。例如:
- 如果路径上贝壳种类是 `[2, 1, 2, 3]`,则不同的贝壳种类数是 `3`(种类分别是 `2`, `1`, 和 `3`)。
- 如果路径上贝壳种类是 `[1, 1, 1]`,则不同的贝壳种类数是 `1`(只有 `1` 一种贝壳)。
要回答这个问题,必须遍历 \( u \) 到 \( v \) 的路径,统计路径上不同贝壳种类的数量。

3
src/20241116/U498417.cpp Normal file
View File

@ -0,0 +1,3 @@
int main(){
}