update
This commit is contained in:
parent
3138c83120
commit
7d0dac0d49
@ -20,8 +20,6 @@ void fd_ft(const ll &ft, const ll &now){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int main(){
|
int main(){
|
||||||
std::iostream::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
|
std::iostream::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
|
||||||
|
|
||||||
|
@ -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
3
src/20241116/U498417.cpp
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
int main(){
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user