learn segment tree
This commit is contained in:
parent
adf9c01090
commit
d7910527f5
2
day7/P5431/1.in
Normal file
2
day7/P5431/1.in
Normal file
@ -0,0 +1,2 @@
|
||||
6 233 42
|
||||
1 4 2 8 5 7
|
1
day7/P5431/1.out
Normal file
1
day7/P5431/1.out
Normal file
@ -0,0 +1 @@
|
||||
91
|
89
day7/P5431/P5431.cpp
Normal file
89
day7/P5431/P5431.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
//TLE 30
|
||||
|
||||
#include<bits/stdc++.h>
|
||||
using namespace std;
|
||||
#define int long long
|
||||
|
||||
const int MAX_N =5e6+5;
|
||||
|
||||
int binExp(int b,int e,int p){
|
||||
int r=1;
|
||||
while(e>0){
|
||||
if(e%2==1)r=(r*b)%p;
|
||||
b=(b*b)%p;
|
||||
e=e>>1;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int inverse(int b,int p){
|
||||
return binExp(b, p-2,p);
|
||||
}
|
||||
|
||||
#ifdef OITEST
|
||||
#endif
|
||||
#ifndef OITEST
|
||||
#endif
|
||||
|
||||
#ifndef OITEST
|
||||
int read(){
|
||||
int x=0,w=1;
|
||||
char ch=0;
|
||||
while(!isdigit(ch)){
|
||||
if(ch=='-')w=-1;
|
||||
ch=getchar();
|
||||
}
|
||||
while(isdigit(ch)){
|
||||
x=x*10+(ch-'0');
|
||||
ch=getchar();
|
||||
}
|
||||
return x*w;
|
||||
}
|
||||
#endif
|
||||
|
||||
signed main(signed argc,char *argv[]){
|
||||
ios::sync_with_stdio(false);
|
||||
cin.tie(0);
|
||||
#ifdef OITEST
|
||||
assert(argc==3);
|
||||
cout<<argv[1]<<endl<<argv[2]<<endl;
|
||||
ifstream ifile(argv[1]);
|
||||
ifstream afile(argv[2]);
|
||||
assert(ifile.is_open()&&afile.is_open()==true);
|
||||
stringstream ss;
|
||||
#define cin ifile
|
||||
#define cout ss
|
||||
#endif
|
||||
|
||||
int n,p,k;
|
||||
|
||||
#ifdef OITEST
|
||||
cin>>n>>p>>k;
|
||||
#endif
|
||||
|
||||
#ifndef OITEST
|
||||
n=read();
|
||||
p=read();
|
||||
k=read();
|
||||
#endif
|
||||
int ans=0;
|
||||
int kp=k;
|
||||
for(int i=1;i<=n;i++){
|
||||
int a;
|
||||
#ifdef OITEST
|
||||
cin>>a;
|
||||
#endif
|
||||
#ifndef OITEST
|
||||
a=read();
|
||||
#endif
|
||||
ans=((int)(kp*inverse(a, p))%p+ans)%p;
|
||||
kp=(kp*k)%p;
|
||||
}
|
||||
cout<<ans<<endl;
|
||||
#ifdef OITEST
|
||||
int my,anser;
|
||||
cout>>my;
|
||||
afile>>anser;
|
||||
assert(my==anser);
|
||||
#endif
|
||||
}
|
84
day7/P5431/chat.md
Normal file
84
day7/P5431/chat.md
Normal file
@ -0,0 +1,84 @@
|
||||
以下是上述问题的 C++ 实现代码,使用了快速幂计算逆元和高效的 IO 处理方式,以应对可能的大规模数据输入。
|
||||
|
||||
### C++ 实现
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
// 快速幂计算 (base^exp) % mod
|
||||
long long mod_exp(long long base, long long exp, long long mod) {
|
||||
long long result = 1;
|
||||
while (exp > 0) {
|
||||
if (exp % 2 == 1) {
|
||||
result = (result * base) % mod;
|
||||
}
|
||||
base = (base * base) % mod;
|
||||
exp /= 2;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 模逆元计算: a^(p-2) % p
|
||||
long long mod_inverse(long long a, long long p) {
|
||||
return mod_exp(a, p - 2, p);
|
||||
}
|
||||
|
||||
int main() {
|
||||
ios::sync_with_stdio(false); // 提高cin/cout的效率
|
||||
cin.tie(0); // 取消cin与cout的关联
|
||||
|
||||
int n;
|
||||
long long p, k;
|
||||
cin >> n >> p >> k;
|
||||
|
||||
long long result = 0;
|
||||
long long k_power = 1; // k^i
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
long long a_i;
|
||||
cin >> a_i;
|
||||
|
||||
long long inv_a = mod_inverse(a_i, p);
|
||||
long long term = (k_power * inv_a) % p;
|
||||
result = (result + term) % p;
|
||||
|
||||
// 更新k_power
|
||||
k_power = (k_power * k) % p;
|
||||
}
|
||||
|
||||
cout << result << endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 代码解释
|
||||
|
||||
1. **快速幂函数 `mod_exp`**:通过二进制分解指数,实现快速计算幂次取模。
|
||||
2. **模逆元函数 `mod_inverse`**:利用费马小定理,通过快速幂计算模逆元。
|
||||
3. **高效的输入输出**:
|
||||
- `ios::sync_with_stdio(false)` 关闭 C++ 的 IO 与 C 的 IO 之间的同步,提高 IO 效率。
|
||||
- `cin.tie(0)` 解除 `cin` 与 `cout` 的关联,避免在每次 `cin` 之前执行 `cout`,进一步提升效率。
|
||||
4. **主函数逻辑**:
|
||||
- 读取输入的 `n`, `p`, `k`,以及数组 `a_i`。
|
||||
- 逐步计算每个 $k^i \times a_i^{-1} \mod p$ 并累加到结果中。
|
||||
- 最终输出累加结果。
|
||||
|
||||
### 样例测试
|
||||
|
||||
使用样例输入:
|
||||
|
||||
```
|
||||
6 233 42
|
||||
1 4 2 8 5 7
|
||||
```
|
||||
|
||||
程序将输出:
|
||||
|
||||
```
|
||||
91
|
||||
```
|
||||
|
||||
### 性能优化
|
||||
|
||||
在 `n` 非常大的情况下,使用上述 C++ 实现,可以确保在合理时间内完成计算。此实现充分利用了快速幂和费马小定理的性质,以及 C++ 的高效输入输出特性。
|
8
day7/SegmentTree/SegmentTree.cpp
Normal file
8
day7/SegmentTree/SegmentTree.cpp
Normal file
@ -0,0 +1,8 @@
|
||||
#include<bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
|
||||
|
||||
int main(){
|
||||
|
||||
}
|
305
day7/SegmentTree/SegmentTree.md
Normal file
305
day7/SegmentTree/SegmentTree.md
Normal file
@ -0,0 +1,305 @@
|
||||
线段树(Segment Tree)是一种用于处理区间查询与更新问题的数据结构。它在处理动态数组或序列时特别有效,能够在对区间进行查询(如求和、最小值、最大值)以及更新(如修改数组中的某个元素)时,提供高效的时间复杂度。
|
||||
|
||||
## 线段树的特点
|
||||
|
||||
1. **时间复杂度**:
|
||||
- **构建**: 构建线段树的时间复杂度是 \(O(n)\),其中 \(n\) 是数组的长度。
|
||||
- **查询**: 线段树可以在 \(O(\log n)\) 时间内完成区间查询操作。
|
||||
- **更新**: 单点更新也可以在 \(O(\log n)\) 时间内完成。
|
||||
|
||||
2. **存储结构**:
|
||||
- 线段树通常使用一个基于数组的完全二叉树来存储,每个节点表示一个区间的某种属性(如和、最大值、最小值等)。
|
||||
- 如果原数组的大小是 \(n\),则线段树的大小约为 \(4n\),但实际应用中,大部分实现都使用 \(2n\) 的大小。
|
||||
|
||||
## 线段树的构建
|
||||
|
||||
假设我们有一个数组 `arr`,它的长度为 `n`,我们需要构建一棵线段树来支持区间查询和更新。
|
||||
|
||||
### 基本思想
|
||||
|
||||
- 线段树的每个叶子节点表示数组中的一个元素。
|
||||
- 非叶子节点表示对应区间中的某个属性(如区间和、区间最小值等)。
|
||||
|
||||
### 构建过程
|
||||
|
||||
1. **递归分治**:
|
||||
- 将区间 `[l, r]` 分成两个子区间 `[l, mid]` 和 `[mid+1, r]`。
|
||||
- 递归构建左右子树,直到区间长度为 1(即叶子节点)。
|
||||
|
||||
2. **合并**:
|
||||
- 在返回的过程中,将左右子区间的值合并为当前区间的值。
|
||||
|
||||
### 示例
|
||||
|
||||
假设我们要构建一个表示区间和的线段树,对于数组 `arr = [1, 3, 5, 7, 9, 11]`:
|
||||
|
||||
1. 构建区间 `[0, 5]` 的线段树,将其分为 `[0, 2]` 和 `[3, 5]` 两部分。
|
||||
2. 继续递归分治,直到处理单个元素。
|
||||
3. 然后,从底向上合并,构建完整的线段树。
|
||||
|
||||
## 线段树的查询与更新
|
||||
|
||||
### 查询
|
||||
|
||||
线段树的查询过程也是基于递归的。对于查询区间 `[l, r]`:
|
||||
|
||||
1. 如果当前区间与查询区间没有交集,返回一个默认值(如 0 对于求和)。
|
||||
2. 如果当前区间完全包含在查询区间中,返回当前节点的值。
|
||||
3. 否则,将当前区间分成两个子区间,递归查询,并将结果合并。
|
||||
|
||||
### 更新
|
||||
|
||||
更新过程类似:
|
||||
|
||||
1. 找到对应叶子节点,更新值。
|
||||
2. 向上递归更新父节点,直到根节点。
|
||||
|
||||
## 总结
|
||||
|
||||
线段树是处理区间操作的强大工具,特别适用于需要频繁查询和更新的大规模数据场景。它的高效性来自于其树形结构,使得每次查询和更新都只需要访问 \(O(\log n)\) 个节点。
|
||||
|
||||
在实际应用中,线段树可以处理很多种类的区间查询问题,包括区间和、区间最小值、区间最大值等,并且可以通过修改合并操作来适应不同的需求。
|
||||
|
||||
下面是一段使用C++实现线段树来处理区间最小值查询问题的代码。我们将逐行解释代码的原理和操作。
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <algorithm> // 用于 std::min 函数
|
||||
|
||||
using namespace std;
|
||||
|
||||
class SegmentTree {
|
||||
private:
|
||||
vector<int> tree;
|
||||
int n;
|
||||
|
||||
// 构建线段树,初始化区间 [start, end] 的节点 idx
|
||||
void buildTree(const vector<int>& arr, int start, int end, int idx) {
|
||||
if (start == end) {
|
||||
// 叶子节点表示单个元素
|
||||
tree[idx] = arr[start];
|
||||
} else {
|
||||
int mid = start + (end - start) / 2;
|
||||
// 递归构建左右子树
|
||||
buildTree(arr, start, mid, 2 * idx + 1);
|
||||
buildTree(arr, mid + 1, end, 2 * idx + 2);
|
||||
// 当前节点存储左右子树的最小值
|
||||
tree[idx] = min(tree[2 * idx + 1], tree[2 * idx + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
// 递归查询区间 [l, r] 的最小值
|
||||
int queryMin(int start, int end, int l, int r, int idx) {
|
||||
if (l <= start && r >= end) {
|
||||
// 完全覆盖,直接返回当前区间的最小值
|
||||
return tree[idx];
|
||||
}
|
||||
if (end < l || start > r) {
|
||||
// 没有交集,返回一个无穷大的值
|
||||
return INT_MAX;
|
||||
}
|
||||
// 部分覆盖,查询左右子区间
|
||||
int mid = start + (end - start) / 2;
|
||||
return min(queryMin(start, mid, l, r, 2 * idx + 1),
|
||||
queryMin(mid + 1, end, l, r, 2 * idx + 2));
|
||||
}
|
||||
|
||||
// 递归更新节点,修改位置 pos 的值为 newVal
|
||||
void updateValue(int start, int end, int pos, int newVal, int idx) {
|
||||
if (start == end) {
|
||||
// 更新叶子节点
|
||||
tree[idx] = newVal;
|
||||
} else {
|
||||
int mid = start + (end - start) / 2;
|
||||
if (pos <= mid) {
|
||||
// 更新左子树
|
||||
updateValue(start, mid, pos, newVal, 2 * idx + 1);
|
||||
} else {
|
||||
// 更新右子树
|
||||
updateValue(mid + 1, end, pos, newVal, 2 * idx + 2);
|
||||
}
|
||||
// 更新当前节点为左右子树的最小值
|
||||
tree[idx] = min(tree[2 * idx + 1], tree[2 * idx + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// 构造函数
|
||||
SegmentTree(const vector<int>& arr) {
|
||||
n = arr.size();
|
||||
tree.resize(4 * n); // 预留足够的空间
|
||||
buildTree(arr, 0, n - 1, 0);
|
||||
}
|
||||
|
||||
// 对外暴露的查询接口,查询区间 [l, r] 的最小值
|
||||
int queryMin(int l, int r) {
|
||||
return queryMin(0, n - 1, l, r, 0);
|
||||
}
|
||||
|
||||
// 对外暴露的更新接口,将位置 pos 的值更新为 newVal
|
||||
void updateValue(int pos, int newVal) {
|
||||
updateValue(0, n - 1, pos, newVal, 0);
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
vector<int> arr = {2, 5, 1, 4, 9, 3};
|
||||
SegmentTree segTree(arr);
|
||||
|
||||
cout << "Initial minimum in range [1, 4]: " << segTree.queryMin(1, 4) << endl; // 输出 1
|
||||
|
||||
segTree.updateValue(3, 0); // 更新 arr[3] 为 0
|
||||
cout << "Minimum in range [1, 4] after update: " << segTree.queryMin(1, 4) << endl; // 输出 0
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 代码解释
|
||||
|
||||
### 1. 导入库与定义
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <algorithm> // 用于 std::min 函数
|
||||
```
|
||||
- 导入标准库,用于输入输出、容器操作以及求最小值函数 `std::min`。
|
||||
|
||||
### 2. 线段树类定义
|
||||
|
||||
```cpp
|
||||
class SegmentTree {
|
||||
private:
|
||||
vector<int> tree;
|
||||
int n;
|
||||
```
|
||||
- 定义一个 `SegmentTree` 类,私有成员包括存储线段树的 `tree` 数组和原始数组大小 `n`。
|
||||
|
||||
### 3. 线段树的构建
|
||||
|
||||
```cpp
|
||||
void buildTree(const vector<int>& arr, int start, int end, int idx) {
|
||||
if (start == end) {
|
||||
tree[idx] = arr[start];
|
||||
} else {
|
||||
int mid = start + (end - start) / 2;
|
||||
buildTree(arr, start, mid, 2 * idx + 1);
|
||||
buildTree(arr, mid + 1, end, 2 * idx + 2);
|
||||
tree[idx] = min(tree[2 * idx + 1], tree[2 * idx + 2]);
|
||||
}
|
||||
}
|
||||
```
|
||||
- `buildTree` 函数递归地构建线段树。
|
||||
- 如果区间 `[start, end]` 只包含一个元素(`start == end`),则将该元素存入树的对应位置。
|
||||
- 如果区间大于1,则将区间分为两部分,递归构建左、右子树,并将两个子区间的最小值存入当前节点。
|
||||
|
||||
### 4. 区间最小值查询
|
||||
|
||||
```cpp
|
||||
int queryMin(int start, int end, int l, int r, int idx) {
|
||||
if (l <= start && r >= end) {
|
||||
return tree[idx];
|
||||
}
|
||||
if (end < l || start > r) {
|
||||
return INT_MAX;
|
||||
}
|
||||
int mid = start + (end - start) / 2;
|
||||
return min(queryMin(start, mid, l, r, 2 * idx + 1),
|
||||
queryMin(mid + 1, end, l, r, 2 * idx + 2));
|
||||
}
|
||||
```
|
||||
- `queryMin` 函数在 `[l, r]` 区间查询最小值。
|
||||
- 如果当前区间完全在查询区间内,直接返回当前节点的值。
|
||||
- 如果没有交集,返回 `INT_MAX` 作为无效值。
|
||||
- 如果部分重叠,递归查询左、右子区间,合并结果。
|
||||
|
||||
### 5. 更新操作
|
||||
|
||||
```cpp
|
||||
void updateValue(int start, int end, int pos, int newVal, int idx) {
|
||||
if (start == end) {
|
||||
tree[idx] = newVal;
|
||||
} else {
|
||||
int mid = start + (end - start) / 2;
|
||||
if (pos <= mid) {
|
||||
updateValue(start, mid, pos, newVal, 2 * idx + 1);
|
||||
} else {
|
||||
updateValue(mid + 1, end, pos, newVal, 2 * idx + 2);
|
||||
}
|
||||
tree[idx] = min(tree[2 * idx + 1], tree[2 * idx + 2]);
|
||||
}
|
||||
}
|
||||
```
|
||||
- `updateValue` 函数递归更新某个位置的值。
|
||||
- 通过递归找到需要更新的叶子节点并修改其值。
|
||||
- 更新完成后,向上递归更新其父节点,确保父节点值的正确性。
|
||||
|
||||
### 6. 构造函数
|
||||
|
||||
```cpp
|
||||
SegmentTree(const vector<int>& arr) {
|
||||
n = arr.size();
|
||||
tree.resize(4 * n);
|
||||
buildTree(arr, 0, n - 1, 0);
|
||||
}
|
||||
```
|
||||
- 构造函数初始化线段树。
|
||||
- `n` 为数组大小,`tree` 的大小设置为 `4 * n` 以确保足够的空间构建完全二叉树。
|
||||
- 调用 `buildTree` 函数从原始数组构建线段树。
|
||||
|
||||
### 7. 对外接口
|
||||
|
||||
```cpp
|
||||
int queryMin(int l, int r) {
|
||||
return queryMin(0, n - 1, l, r, 0);
|
||||
}
|
||||
|
||||
void updateValue(int pos, int newVal) {
|
||||
updateValue(0, n - 1, pos, newVal, 0);
|
||||
}
|
||||
```
|
||||
- `queryMin` 和 `updateValue` 是对外暴露的接口,用于查询最小值和更新数组值。
|
||||
|
||||
### 8. 测试代码
|
||||
|
||||
```cpp
|
||||
int main() {
|
||||
vector<int> arr = {2, 5, 1, 4, 9, 3};
|
||||
SegmentTree segTree(arr);
|
||||
|
||||
cout << "Initial minimum in range [1, 4]: " << segTree.queryMin(1, 4) << endl; // 输出 1
|
||||
|
||||
segTree.updateValue(3, 0); // 更新 arr[3] 为 0
|
||||
cout << "Minimum in range [1,
|
||||
|
||||
4] after update: " << segTree.queryMin(1, 4) << endl; // 输出 0
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
- 创建一个数组,并构建相应的线段树。
|
||||
- 查询区间 `[1, 4]` 的最小值,初始查询得到 1。
|
||||
- 更新数组中的一个值(将 `arr[3]` 从 4 更新为 0),再次查询该区间,结果变为 0。
|
||||
在这段代码中,`idx` 是一个参数,用于表示线段树中某个节点的索引。它指向线段树中某个节点的位置,用来追踪当前递归所处理的节点在 `tree` 数组中的位置。
|
||||
|
||||
### `idx` 的作用
|
||||
|
||||
- **在构建线段树时**:
|
||||
- `idx` 用于标识当前节点在 `tree` 数组中的位置。
|
||||
- 在递归过程中,通过计算左子节点和右子节点的索引 (`2 * idx + 1` 和 `2 * idx + 2`),来构建整棵树。
|
||||
|
||||
- **在查询和更新操作中**:
|
||||
- `idx` 也是用来指示当前节点的位置。通过递归传递 `idx` 参数,程序能够定位到需要操作的具体节点。
|
||||
- 对于查询操作,`idx` 表示当前递归所在的节点;对于更新操作,`idx` 表示需要更新的节点。
|
||||
|
||||
### 具体示例
|
||||
|
||||
假设有一个包含 6 个元素的数组 `arr = {2, 5, 1, 4, 9, 3}`,在构建线段树时:
|
||||
|
||||
- `idx = 0` 表示根节点,负责管理整个数组 `[0, 5]`。
|
||||
- 递归过程中,`idx = 1` 可能对应左子树,管理数组的前半部分 `[0, 2]`,而 `idx = 2` 可能对应右子树,管理数组的后半部分 `[3, 5]`。
|
||||
- 继续递归,`idx = 3` 可能表示 `[0, 1]`,`idx = 4` 表示 `[2, 2]` 等等。
|
||||
|
||||
因此,`idx` 就是线段树中节点的唯一标识符,通过它可以在 `tree` 数组中找到相应节点的位置。
|
BIN
day7/SegmentTree/SegmentTree.pdf
Normal file
BIN
day7/SegmentTree/SegmentTree.pdf
Normal file
Binary file not shown.
17
xmake.lua
17
xmake.lua
@ -32,7 +32,7 @@ target("fperfect")
|
||||
set_rundir("./day5/perfect/")
|
||||
for val=1,4 do
|
||||
local strval = tostring(val)
|
||||
add_tests(strval,{files="./day5/perfect/fix.cpp",defines="OITEST",runargs=strval,run_timeout=2000})
|
||||
add_tests(strval,{files="./day5/perfect/fix.cpp",defines="OITEST",runargs=strval,run_timeout=1000})
|
||||
end
|
||||
|
||||
target("pre88")
|
||||
@ -46,4 +46,17 @@ target("inverse")
|
||||
for v=1,2 do
|
||||
local strname = tostring(v)
|
||||
add_tests(strname,{files="./day7/inverse/*.cpp",defines="OITEST",runargs=strname,run_timeout=1000})
|
||||
end
|
||||
end
|
||||
|
||||
target("P5431")
|
||||
set_rundir("./day7/P5431")
|
||||
add_files("./day7/P5431/*.cpp")
|
||||
for v=1,1 do
|
||||
local s=tostring(v)
|
||||
add_tests(s,{files="./day7/P5431/*.cpp",defines="OITEST",runargs={s..".in",s..".out"},run_timeout=1000})
|
||||
end
|
||||
|
||||
target("segtree")
|
||||
set_rundir("./day7/SegmentTree")
|
||||
add_files("./day7/SegmentTree/*.cpp")
|
||||
|
Loading…
Reference in New Issue
Block a user