diff --git a/README.md b/README.md index e6ec0af..76e3494 100644 --- a/README.md +++ b/README.md @@ -76,12 +76,24 @@ ll ksm(ll a,ll b,ll M){ ## Day6 [CSP-S1 模拟赛.pdf](./day6/CSP-S1%20模拟赛.pdf) >??:4.13.14.20.21 -## T9 +### T9 1. 先与后或先&后| +### 快速幂学习笔记 +[cpp模板](./day6/binaryExponentiation/binaryExponentiation.cpp) + ## Day7 ### 离线算法和在线算法 >有预处理和没有预处理的区别 +### 逆元:学习笔记 +[markdown](./day7/inverse/inverse_by_chat.md) +[pdf](./day7/inverse/inverse_by_chat.pdf) +### 线段树:学习笔记 +[mardown](./day7/SegmentTree/SegmentTree.md) +[pdf](./day7/SegmentTree/SegmentTree.pdf) +### 待学: +>扫描线 +>矩阵乘法 # 排序 ## 稳定性 diff --git a/day7/SegmentTree/1.in b/day7/SegmentTree/1.in new file mode 100644 index 0000000..65d6b10 --- /dev/null +++ b/day7/SegmentTree/1.in @@ -0,0 +1,6 @@ +5 +10 11 12 13 14 +3 +1 2 +2 3 +1 5 \ No newline at end of file diff --git a/day7/SegmentTree/1.out b/day7/SegmentTree/1.out new file mode 100644 index 0000000..8579d56 --- /dev/null +++ b/day7/SegmentTree/1.out @@ -0,0 +1,3 @@ +21 +23 +60 \ No newline at end of file diff --git a/day7/SegmentTree/SegmentTree.cpp b/day7/SegmentTree/SegmentTree.cpp index 15fe60d..e69de29 100644 --- a/day7/SegmentTree/SegmentTree.cpp +++ b/day7/SegmentTree/SegmentTree.cpp @@ -1,8 +0,0 @@ -#include -using namespace std; - - - -int main(){ - -} \ No newline at end of file diff --git a/day7/SegmentTree/SegmentTree.cpp.old b/day7/SegmentTree/SegmentTree.cpp.old new file mode 100644 index 0000000..8320bb6 --- /dev/null +++ b/day7/SegmentTree/SegmentTree.cpp.old @@ -0,0 +1,68 @@ +#include +using namespace std; + +// #define ASSERTM(c,m){if(!(c)){cerr<<"assert failed :"<<#c<<" "<>1); + build(t,a,s,m,n*2); + build(t,a,m+1,e,n*2+1); + t[n]=t[n*2]+t[n*2+1]; +} + +int query(int t[],int s,int e,int n,int l,int r){ + if(l<=s&&e<=r){ + return t[n]; + } + if(e>1),sum=0; + sum+=query(t, s, m, n*2, l, r); + sum+=query(t, m+1, e, n*2+1, l, r); + return sum; +} + +int main(int argc,char *argv[]){ + ASSERT(argc==3); + ifstream ifile(argv[1]); + ifstream afile(argv[2]); + ASSERT(ifile.is_open()==true) + ASSERT(afile.is_open()==true) + int n; + ifile>>n; + int a[n+1]; + int t[n*4+1]; + for(int i=1;i<=n;i++){ + ifile>>a[i]; + } + build(t,a,1,n,1); + // for(int i=1;i<=n*4;i++){ + // PRINT_VALUE(i); + // PRINT_VALUE(t[i]); + // } + int m; + ifile>>m; + for(int i=1;i<=m;i++){ + int cans; + afile>>cans; + int a,b; + ifile>>a>>b; + int myans; + // myans=query(t,1,n,1,a,b); + PRINT_VALUE((myans=query(t,1,n,1,a,b),myans)); + ASSERT(cans==myans) + } +} \ No newline at end of file diff --git a/day7/SegmentTree/SegmentTree.md b/day7/SegmentTree/SegmentTree.md index 6fbb1a3..734ebf9 100644 --- a/day7/SegmentTree/SegmentTree.md +++ b/day7/SegmentTree/SegmentTree.md @@ -302,4 +302,218 @@ int main() { - 递归过程中,`idx = 1` 可能对应左子树,管理数组的前半部分 `[0, 2]`,而 `idx = 2` 可能对应右子树,管理数组的后半部分 `[3, 5]`。 - 继续递归,`idx = 3` 可能表示 `[0, 1]`,`idx = 4` 表示 `[2, 2]` 等等。 -因此,`idx` 就是线段树中节点的唯一标识符,通过它可以在 `tree` 数组中找到相应节点的位置。 \ No newline at end of file +因此,`idx` 就是线段树中节点的唯一标识符,通过它可以在 `tree` 数组中找到相应节点的位置。 + +为了更详细地解释如何构建线段树,我将使用从1开始的数组编号,并逐行解释代码的每一步。假设我们有一个数组 `arr = {2, 5, 1, 4, 9, 3}`,它的索引从1开始。 + +### 线段树的构建 + +我们将重点放在 `buildTree` 函数上,这个函数的作用是递归地构建线段树。下面是该函数的代码,以及每行代码的详细解释。 + +```cpp +void buildTree(const vector& 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]); + } +} +``` + +### 参数说明 + +- `arr`:原始数组,包含要构建线段树的数据。 +- `start`:当前处理的区间的起始索引(从1开始)。 +- `end`:当前处理的区间的结束索引(从1开始)。 +- `idx`:当前节点在线段树数组 `tree` 中的位置。 + +### 例子 + +假设我们有数组 `arr = {2, 5, 1, 4, 9, 3}`,我们将构建一个线段树来处理区间最小值查询。 + +1. **初始调用**:`buildTree(arr, 1, 6, 0)` 对应处理区间 `[1, 6]`,即整个数组。 + + 这意味着根节点 `idx = 0` 管理整个数组 `[1, 6]` 的最小值。 + +2. **叶子节点处理**: + ```cpp + if (start == end) { + tree[idx] = arr[start]; + } + ``` + - 如果 `start` 和 `end` 相等,表示当前区间只有一个元素,这是一个叶子节点。 + - 直接将该元素存入 `tree` 数组的 `idx` 位置。 + + **示例**:当 `start = 3`,`end = 3` 时,调用 `buildTree(arr, 3, 3, 4)`,此时 `idx = 4`,这意味着我们处理的是区间 `[3, 3]`,即数组 `arr[3]`。我们将 `tree[4]` 设置为 `arr[3]` 的值 `1`。 + +3. **递归构建左子树和右子树**: + ```cpp + int mid = start + (end - start) / 2; + buildTree(arr, start, mid, 2 * idx + 1); + buildTree(arr, mid + 1, end, 2 * idx + 2); + ``` + - 我们首先计算当前区间的中点 `mid`,将区间分成左右两部分。 + - `mid` 的计算方式是 `mid = start + (end - start) / 2`。例如,对于 `[1, 6]`,`mid = 1 + (6 - 1) / 2 = 3`,所以区间分为 `[1, 3]` 和 `[4, 6]`。 + - 递归调用 `buildTree` 函数来构建左子树和右子树。 + - 左子树的索引为 `2 * idx + 1`,右子树的索引为 `2 * idx + 2`。 + + **示例**:对于根节点(`idx = 0`),区间 `[1, 6]` 被分为 `[1, 3]` 和 `[4, 6]`,接着递归构建左子树 `buildTree(arr, 1, 3, 1)` 和右子树 `buildTree(arr, 4, 6, 2)`。 + +4. **合并结果**: + ```cpp + tree[idx] = min(tree[2 * idx + 1], tree[2 * idx + 2]); + ``` + - 当左子树和右子树构建完成后,将它们的最小值合并为当前节点的值。 + - `tree[idx]` 保存的是左右子树中最小的值。 + + **示例**:假设 `buildTree(arr, 1, 3, 1)` 处理区间 `[1, 3]` 返回的左子树最小值为 `1`,`buildTree(arr, 4, 6, 2)` 处理区间 `[4, 6]` 返回的右子树最小值为 `3`,那么根节点 `tree[0]` 将被设为 `min(1, 3) = 1`。 + +### 完整树的结构 + +通过递归调用,最终我们将得到一棵线段树。假设 `arr = {2, 5, 1, 4, 9, 3}`,用1开始的索引来表示: + +- 根节点 `tree[0]` 管理整个数组 `[1, 6]`,存储最小值 `1`。 +- 左子树 `tree[1]` 管理 `[1, 3]`,存储最小值 `1`。 +- 右子树 `tree[2]` 管理 `[4, 6]`,存储最小值 `3`。 + +逐渐递归下去,直到所有叶子节点都填满为止。 + +### 总结 + +每个节点的 `idx` 表示线段树中节点在数组 `tree` 中的位置。通过递归分治法,我们将整个区间分成若干子区间,逐步构建线段树的每个节点。最终,树的根节点(`tree[0]`)存储了整个数组的最小值,树的其他节点存储了各自子区间的最小值。 + +在线段树中,搜索过程通常指的是查询某个区间内的最小值、最大值或其他聚合信息。为了更好地理解搜索过程,我们将详细讲解如何使用线段树来查询一个给定区间内的最小值,并通过逐步解析代码的方式说明每一步的具体操作。 + +### 问题描述 + +假设我们有一个从1开始编号的数组 `arr = {2, 5, 1, 4, 9, 3}`,我们希望使用线段树来查询任意给定区间 `[l, r]` 内的最小值。 + +### 查询函数:`queryMin` + +我们使用如下函数来查询区间 `[l, r]` 内的最小值: + +```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)); +} +``` + +### 参数说明 + +- `start`:当前节点管理的区间起始索引。 +- `end`:当前节点管理的区间结束索引。 +- `l` 和 `r`:查询的目标区间 `[l, r]`。 +- `idx`:当前节点在 `tree` 数组中的位置。 + +### 查询过程的详细解释 + +假设我们要查询区间 `[2, 5]` 的最小值,初始调用 `queryMin(1, 6, 2, 5, 0)`,即从根节点开始搜索。 + +1. **判断当前区间是否完全覆盖查询区间**: + ```cpp + if (l <= start && r >= end) { + return tree[idx]; + } + ``` + - 如果当前节点管理的区间 `[start, end]` 完全包含在查询区间 `[l, r]` 内(即 `l <= start` 且 `r >= end`),那么当前节点存储的值就是该区间的最小值,直接返回 `tree[idx]`。 + + **示例**:如果我们查询区间 `[2, 5]`,而当前节点管理的是 `[4, 6]`,此时 `start = 4, end = 6, l = 2, r = 5`,不完全覆盖,所以不返回。 + +2. **判断当前区间与查询区间是否没有交集**: + ```cpp + if (end < l || start > r) { + return INT_MAX; + } + ``` + - 如果当前区间与查询区间没有任何交集(即 `end < l` 或 `start > r`),这意味着当前节点不需要参与最小值的计算,返回一个极大值 `INT_MAX`(表示无效值)。 + + **示例**:如果当前节点管理的区间 `[1, 1]`,此时 `start = 1, end = 1, l = 2, r = 5`,显然没有交集,返回 `INT_MAX`。 + +3. **部分覆盖:递归查询左右子区间**: + ```cpp + 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)); + ``` + - 如果当前区间 `[start, end]` 和查询区间 `[l, r]` 部分重叠,继续递归查询左右子树。 + - 首先计算当前区间的中点 `mid`,将当前区间 `[start, end]` 分为 `[start, mid]` 和 `[mid + 1, end]` 两个子区间。 + - 递归查询左子树和右子树的最小值,然后返回这两个子区间的最小值作为结果。 + + **示例**:查询区间 `[2, 5]`,当前节点管理区间 `[1, 6]`,将其分为 `[1, 3]` 和 `[4, 6]`,然后递归查询左右子区间,最后返回两个子区间的最小值。 + +### 完整查询过程示例 + +假设我们要查询 `arr` 中 `[2, 5]` 的最小值,调用 `queryMin(1, 6, 2, 5, 0)`。以下是递归查询的详细步骤: + +1. **根节点**: + - `start = 1, end = 6, l = 2, r = 5, idx = 0`。 + - 当前区间 `[1, 6]` 与查询区间 `[2, 5]` 部分重叠,继续递归查询。 + - 计算 `mid = 1 + (6 - 1) / 2 = 3`。 + - 查询左子树 `[1, 3]` 和右子树 `[4, 6]`。 + +2. **左子树 `[1, 3]`**: + - `start = 1, end = 3, l = 2, r = 5, idx = 1`。 + - 当前区间 `[1, 3]` 与查询区间 `[2, 5]` 部分重叠,继续递归查询。 + - 计算 `mid = 1 + (3 - 1) / 2 = 2`。 + - 查询左子树 `[1, 2]` 和右子树 `[3, 3]`。 + +3. **左子树的左子树 `[1, 2]`**: + - `start = 1, end = 2, l = 2, r = 5, idx = 3`。 + - 当前区间 `[1, 2]` 与查询区间 `[2, 5]` 部分重叠,继续递归查询。 + - 计算 `mid = 1 + (2 - 1) / 2 = 1`。 + - 查询左子树 `[1, 1]` 和右子树 `[2, 2]`。 + +4. **到达叶子节点 `[1, 1]`**: + - `start = 1, end = 1, l = 2, r = 5, idx = 7`。 + - 当前区间 `[1, 1]` 与查询区间 `[2, 5]` 无交集,返回 `INT_MAX`。 + +5. **叶子节点 `[2, 2]`**: + - `start = 2, end = 2, l = 2, r = 5, idx = 8`。 + - 当前区间 `[2, 2]` 完全包含在查询区间 `[2, 5]` 内,返回 `tree[8] = 5`。 + +6. **回到节点 `[1, 2]`**: + - 左子树返回 `INT_MAX`,右子树返回 `5`,取 `min(INT_MAX, 5) = 5`。 + +7. **右子树 `[3, 3]`**: + - `start = 3, end = 3, l = 2, r = 5, idx = 4`。 + - 当前区间 `[3, 3]` 完全包含在查询区间 `[2, 5]` 内,返回 `tree[4] = 1`。 + +8. **回到节点 `[1, 3]`**: + - 左子树返回 `5`,右子树返回 `1`,取 `min(5, 1) = 1`。 + +9. **右子树 `[4, 6]`**: + - `start = 4, end = 6, l = 2, r = 5, idx = 2`。 + - 当前区间 `[4, 6]` 与查询区间 `[2, 5]` 部分重叠,继续递归查询。 + - 计算 `mid = 4 + (6 - 4) / 2 = 5`。 + - 查询左子树 `[4, 5]` 和右子树 `[6, 6]`。 + +10. **左子树 `[4, 5]`**: + - `start = 4, end = 5, l = 2, r = 5, idx = 5`。 + - 当前区间 `[4, 5]` 完全包含在查询区间 `[2, 5]` 内,返回 `tree[5] = 4`。 + +11. **右子树 `[6, 6]`**: + - `start = 6, end = 6, l = 2, r = 5, idx = 6`。 + - 当前区间 `[6, 6]` 与 + +查询区间 `[2, 5]` 无交集,返回 `INT_MAX`。 + +12. **回到节点 `[4, 6]`**: + - 左子树返回 `4`,右子树返回 `INT_MAX`,取 `min(4, INT_MAX) = 4`。 + +13. **回到根节点 `[1, 6]`**: + - 左子树返回 `1`,右子树返回 `4`,最终结果为 `min(1, 4) = 1`。 + +### 总结 + +通过递归地将查询区间 `[l, r]` 与线段树节点管理的区间 `[start, end]` 比较,我们可以有效地找到查询区间内的最小值。这个过程利用了线段树的分治思想和区间重叠的特性,确保每次查询的时间复杂度为 `O(log n)`。 \ No newline at end of file diff --git a/day7/SegmentTree/SegmentTree.pdf b/day7/SegmentTree/SegmentTree.pdf index e869c06..09a7379 100644 Binary files a/day7/SegmentTree/SegmentTree.pdf and b/day7/SegmentTree/SegmentTree.pdf differ diff --git a/xmake.lua b/xmake.lua index acae5a4..7ac9152 100644 --- a/xmake.lua +++ b/xmake.lua @@ -59,4 +59,8 @@ target("P5431") target("segtree") set_rundir("./day7/SegmentTree") add_files("./day7/SegmentTree/*.cpp") + for v=1,1 do + local s=tostring(v) + add_tests(s,{files="./day7/SegmentTree/*.cpp",runargs={s..".in",s..".out"},defines="OITEST"}) + end \ No newline at end of file