This commit is contained in:
HuangHai
2025-08-30 18:35:01 +08:00
commit 1f397eca87
6258 changed files with 1487241 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
// ZKW 变异模板解决AcWing 243
// https://codeforces.com/blog/entry/18051
// 普通树段树 323 ms 向上向下
// ZKW 线段树 192 ms 向下向上
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
//快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
const int N = 1e5 + 10;
int n, h, m;
LL sum[N << 1], tag[N << 1];
void build() {
for (h = 1; 1 << h < n;) h++; //计算树高
for (int i = n + 1; i <= 2 * n; i++) sum[i] = read(); //叶子节点赋值
for (int i = n; i; i--) sum[i] = sum[i << 1] + sum[i << 1 | 1]; //统计信息更新
}
void add(int x, int val, int len) {
sum[x] += val * len;
if (x <= n) tag[x] += val; //如果不是是叶子才打标记
}
void pushup(int x) {
int len = 1;
while (x > 1) len <<= 1, x >>= 1, sum[x] = sum[x << 1] + sum[x << 1 | 1] + tag[x] * len;
//从下往上,儿子的值不一定正确,需要累上标记
}
void pushdown(int x) {
for (int i = h, len = 1 << (h - 1); i; i--, len >>= 1) { //从上到下len初始为1<<(h-1)
int y = x >> i;
if (tag[y]) { //下传标记
add(y << 1, tag[y], len), add(y << 1 | 1, tag[y], len);
tag[y] = 0;
}
}
}
void modify(int l, int r, int val) {
l += n, r += n;
int yl = l, yr = r;
for (int len = 1; l <= r; l = (l + 1) >> 1, r = (r - 1) >> 1, len <<= 1) { //从下往上,叶子节点len为1
if (l & 1) add(l, val, len);
if (r & 1 ^ 1) add(r, val, len);
}
pushup(yl), pushup(yr); //别忘了pushup
}
LL query(int l, int r) {
l += n, r += n;
pushdown(l), pushdown(r); //查询前要下传该区间标记
LL res = 0;
for (; l <= r; l = (l + 1) >> 1, r = (r - 1) >> 1) {
if (l & 1) res += sum[l];
if (r & 1 ^ 1) res += sum[r];
}
return res;
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("243.in", "r", stdin);
#endif
n = read(), m = read();
//构建线段树
build();
int x, y, k;
char op[2];
while (m--) {
scanf("%s", op);
x = read(), y = read();
if (op[0] == 'C') {
k = read();
modify(x, y, k);
} else
printf("%lld\n", query(x, y));
}
return 0;
}

View File

@@ -0,0 +1,7 @@
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4

View File

@@ -0,0 +1,107 @@
/*
延绵的山峰
★★☆ 输入文件:climb.in 输出文件:climb.out 简单对比
时间限制:1 s 内存限制:512 MB
问题描述
有一座延绵不断、跌宕起伏的山,最低处海拔为0,最高处海拔不超过8848米,从这座山的一端走到另一端的过程中,
每走1米海拔就升高或降低1米。有Q个登山队计划在这座山的不同区段登山,当他们攀到各自区段的最高峰时,就会插上队旗。
请你写一个程序找出他们插旗的高度。
输入文件
第1行,一个整数N(N<=10^6),表示山两端的跨度。
接下来N+1行,每行一个非负整数Hi,表示该位置的海拔高度,其中H0=Hn=0。
然后是一个正整数Q(Q<=7000),表示登山队的数量。
接下来Q行,每行两个数Ai, Bi,表示第i个登山队攀爬的区段[Ai,Bi],其中0<=Ai<=Bi<=N。
输出文件
Q行,每行为一个整数,表示第i个登山队插旗的高度。
样例输入
10
0
1
2
3
2
3
4
3
2
1
0
5
0 10
2 4
3 7
7 9
8 8
样例输出
4
3
4
3
2
*/
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 100010;
// 快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int d[N << 2], m;
//构建zkw线段树
void build(int n) {
for (m = 1; m < n;) m <<= 1; // 强行开够(大于n)方便二进制访问叶节点
for (int i = 1; i <= n; i++) d[m + i] = read(); //对zkw的叶子节点赋值
for (int i = m; i; i--) d[i] = max(d[i << 1], d[i << 1 | 1]); //待所有叶子节点有值后,统一执行一次向上数据统计信息汇总
}
//区间查询最大值
int query(int l, int r) {
int ans = 0;
for (l = l + m - 1, r = r + m + 1; l ^ r ^ 1; l >>= 1, r >>= 1) {
if (~l & 1) ans = max(d[l ^ 1], ans);
if (r & 1) ans = max(d[r ^ 1], ans);
}
return ans;
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("Cogs58.in", "r", stdin);
#endif
int n, q, l, r;
/*
第1行,一个整数N(N<=10^6),表示山两端的跨度。
接下来N+1行,每行一个非负整数Hi,表示该位置的海拔高度,其中H0=Hn=0。
*/
n = read(), n++; // H0~Hn 共n+1行,而且,题目告诉我们H0=Hn=0,会在输入数据时给出,所以,一共是n+1点
//构建
build(n);
q = read();
for (int i = 1; i <= q; i++) {
l = read(), r = read();
printf("%d\n", query(l + 1, r + 1));
}
return 0;
}

View File

@@ -0,0 +1,18 @@
10
0
1
2
3
2
3
4
3
2
1
0
5
0 10
2 4
3 7
7 9
8 8

View File

@@ -0,0 +1,77 @@
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
//快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
const int N = 50000 + 10;
int m = 1, n;
int d[N << 2];
//区间查询
int query(int l, int r) {
int ans = 0;
for (l = l + m - 1, r = r + m + 1; l ^ r ^ 1; l >>= 1, r >>= 1) {
if (~l & 1) ans += d[l ^ 1]; // l是偶数
if (r & 1) ans += d[r ^ 1]; // r是奇数
}
return ans;
}
//单点增加
void add(int x, int c) {
d[m + x] += c;
for (int i = (m + x) >> 1; i; i >>= 1) d[i] = d[i << 1] + d[i << 1 | 1];
}
void build(int n) {
memset(d, 0, sizeof d);
for (m = 1; m < n;) m <<= 1; // 强行开够(大于n)方便二进制访问叶节点
for (int i = 1; i <= n; i++) d[m + i] = read(); //初始值
for (int i = m; i; i--) d[i] = d[i << 1] + d[i << 1 | 1]; //待所有叶子节点有值后,统一执行一次向上数据统计信息汇总
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("HDU1166.in", "r", stdin);
#endif
int k = read();
for (int q = 1; q <= k; q++) {
n = read();
//构建zkw线段树
build(n);
printf("Case %d:\n", q);
char op[10];
while (scanf("%s", op)) {
if (op[0] == 'E') break;
int l = read(), r = read();
if (op[0] == 'Q')
printf("%d\n", query(l, r));
else if (op[0] == 'A')
add(l, r);
else if (op[0] == 'S')
add(l, -r);
}
}
return 0;
}

View File

@@ -0,0 +1,10 @@
1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3
Add 3 6
Query 2 7
Sub 10 2
Add 6 3
Query 3 10
End

View File

@@ -0,0 +1,79 @@
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 2e5 + 1000;
// 快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
// zkw线段树
int d[N << 2];
int n; // n个叶子节点
int q; // q个操作,区间查询或单点修改
int m; // m是指所有父节点们的个数
//单点修改并psuhup
void modify(int x, int c) {
d[m + x] = c;
for (int i = (m + x) >> 1; i; i >>= 1) d[i] = max(d[i << 1], d[i << 1 | 1]);
}
//构建重口味树
void built() {
memset(d, 0, sizeof d);
for (m = 1; m <= n + 1;) m <<= 1; // 强行开够(大于n)方便二进制访问叶节点
for (int i = 1; i <= n; i++) d[m + i] = read(); //初始值
for (int i = m; i; i--) d[i] = max(d[i << 1], d[i << 1 | 1]); //待所有叶子节点有值后,统一执行一次向上数据统计信息汇总
}
//区间查询
int query(int l, int r) {
int ans = -1;
// 闭区间改为开区间:l=l+m-1 : 将查询区间改为l-1, r=r+m+1 : 将查询区间改为r+1
// l^r^1 : 相当于判断l与r是否是兄弟节点
for (l = l + m - 1, r = r + m + 1; l ^ r ^ 1; l >>= 1, r >>= 1) {
if (~l & 1) ans = max(ans, d[l ^ 1]); // l % 2 == 0 左端点 是左儿子 max(右子树),想像一下题解中0号端点(哨兵),肯定是%2=0
if (r & 1) ans = max(ans, d[r ^ 1]); // r % 2 == 1 右端点 是右儿子 max(左子树),想像一下题解中1号端点,肯定是%2=1
}
return ans;
}
//方法重载,单点查询 [本题没有用到,用来做模板的]
int query(int x) {
return d[m + x]; //直接返回结果的zkw线段树节点值
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("HDU1754.in", "r", stdin);
#endif
char op[2];
while (~scanf("%d%d", &n, &q)) {
//构建zkw树
built();
while (q--) {
scanf("%s", op);
int l = read(), r = read();
if (op[0] == 'Q')
printf("%d\n", query(l, r));
else
modify(l, r);
}
}
return 0;
}

View File

@@ -0,0 +1,8 @@
5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5

View File

@@ -0,0 +1,131 @@
##[$B$ - $I$ $Hate$ $It$ $HDU$ - $1754$](http://acm.hdu.edu.cn/showproblem.php?pid=1754)
### 一、题目描述
很多学校流行一种比较的习惯。老师们很喜欢询问,从 **某某** 到 **某某** 当中,分数最高的是多少。这让很多学生很反感。
不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要 **更新某位同学** 的成绩。
**$Input$**
本题目包含多组测试,请处理到文件结束。
在每个测试的第一行,有两个正整数 $N$ 和 $M$ ( $0<N<=200000,0<M<5000$ ),分别代表学生的数目和操作的数目。
学生$ID$编号分别从$1$编到$N$。
第二行包含$N$个整数,代表这$N$个学生的初始成绩,其中第$i$个数代表$ID$为$i$的学生的成绩。
接下来有$M$行。每一行有一个字符 $C$ (只取'$Q$'或'$U$') ,和两个正整数$A,B$。
当$C$为'$Q$'的时候,表示这是一条询问操作,它询问$ID$从$A$到$B$(包括$A,B$)的学生当中,成绩最高的是多少。
当$C$为'$U$'的时候,表示这是一条更新操作,要求把$ID$为$A$的学生的成绩更改为$B$。
**Output**
对于每一次询问操作,在一行里面输出最高成绩。
**Sample Input**
```cpp {.line-numbers}
5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5
```
**Sample Outpu**
```cpp {.line-numbers}
5
6
5
9
```
### 二、解题思路
- 单点修改, 区间查询
```cpp {.line-numbers}
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 2e5 + 1000;
// 快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
// zkw线段树
int d[N << 2];
int n; // n个叶子节点
int q; // q个操作,区间查询或单点修改
int m; // m是指所有父节点们的个数
//构建重口味树
void built() {
memset(d, 0, sizeof d);
for (m = 1; m <= n + 1;) m <<= 1; // 强行开够(大于n)方便二进制访问叶节点
for (int i = m + 1; i <= m + n; i++) d[i] = read(); // n个同学的初始成绩,叶子节点
/*Q:理论上从m开始都是叶节点那为什么不从m开始而是m+1呢?
A:因为在区间查询时最大是[1,n]而这个线段树区间查询需要变成开区间,即变为(0,n+1) 所以当然要给0的位置留个空区间查询
*/
// 父亲们倒序逐个更新,是O(N)
for (int x = m - 1; x; x--) d[x] = max(d[x << 1], d[x << 1 | 1]);
}
//单点修改
void modify(int x, int c) {
//定点更新+向上统计更新,牛B啊~
//这个更新与上面的父亲们逐个更新相比,是单条路径的、爬树的更新过程,O(logN)
for (d[x = x + m] = c, x >>= 1; x; x >>= 1) d[x] = max(d[x << 1], d[x << 1 | 1]);
}
//区间查询
int query(int l, int r) {
int ans = -1;
// 闭区间改为开区间:l=l+m-1 : 将查询区间改为l-1, r=r+m+1 : 将查询区间改为r+1
// l^r^1 : 相当于判断l与r是否是兄弟节点
for (l = l + m - 1, r = r + m + 1; l ^ r ^ 1; l >>= 1, r >>= 1) {
if (~l & 1) ans = max(ans, d[l ^ 1]); // l % 2 == 0 左端点 是左儿子 max(右子树),想像一下题解中0号端点(哨兵),肯定是%2=0
if (r & 1) ans = max(ans, d[r ^ 1]); // r % 2 == 1 右端点 是右儿子 max(左子树),想像一下题解中1号端点,肯定是%2=1
}
return ans;
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("HDU1754.in", "r", stdin);
#endif
char op[2];
while (~scanf("%d%d", &n, &q)) {
//构建zkw树
built();
while (q--) {
scanf("%s", op);
int l = read(), r = read();
if (op[0] == 'Q')
printf("%d\n", query(l, r));
else
modify(l, r);
}
}
return 0;
}
```

View File

@@ -0,0 +1,110 @@
// Luogu.org P1438
// https://www.cnblogs.com/cytus/p/9590071.html
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
const int N = 1e5 + 7;
int n, q, m;
LL a[N], tr[N << 2], add[N << 2];
//快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
void modify(int x, int d) {
for (x += m; x; x >>= 1) tr[x] += d; //单点修改,一路向上更新
}
void modify(int l, int r, int d) { //区间修改
LL lc = 0, rc = 0, c = 1;
for (l = l + m - 1, r = r + m + 1; l ^ r ^ 1; l >>= 1, r >>= 1, c <<= 1) {
tr[l] += d * lc, tr[r] += d * rc;
if (~l & 1) add[l ^ 1] += d, tr[l ^ 1] += d * c, lc += c;
if (r & 1) add[r ^ 1] += d, tr[r ^ 1] += d * c, rc += c;
}
//一路向上走,修改sum值,但却没有修改add
for (; l; l >>= 1, r >>= 1) tr[l] += d * lc, tr[r] += d * rc;
}
LL query(int l, int r) {
LL res = 0, lc = 0, rc = 0, c = 1;
for (l = l + m - 1, r = r + m + 1; l ^ r ^ 1; l >>= 1, r >>= 1, c <<= 1) {
if (add[l]) res += add[l] * lc;
if (add[r]) res += add[r] * rc;
if (~l & 1) res += tr[l ^ 1], lc += c;
if (r & 1) res += tr[r ^ 1], rc += c;
}
for (; l; l >>= 1, r >>= 1) res += add[l] * lc, res += add[r] * rc;
return res;
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("P1438.in", "r", stdin);
#endif
n = read(), q = read();
// build
for (m = 1; m <= n + 1;) m <<= 1;
for (int i = 1; i <= n; i++) {
a[i] = read();
tr[m + i] = a[i] - a[i - 1]; //记录差分值
}
// debug
// printf("m=%d\n", m);
// for (int i = 0; i <= 6; i++) printf("%lld ", tr[m + i]);
// puts("");
//所有父节点,从下向上,将值更新为左右子树的和,每个值都是自已所辖区间的差分和
for (int i = m - 1; i >= 1; i--) {
tr[i] = tr[i << 1] + tr[i << 1 | 1];
// printf("tr[%d]=%d\n", i, tr[i]);
}
/*
1 2 3 4 5
1 2 3 4 5
1 3 5
----------
1 3 6 9 5
query(3)=6
tr
1 1 1 1 1
*/
for (int i = 1; i <= q; i++) {
int op = read();
if (op == 1) {
int l = read(), r = read();
LL k = read(), d = read(); //首项:k,公差:d
// 1 2 4 1 2
modify(l, k); //改2
modify(r + 1, -(k + (r - l) * d)); //改4+1
modify(l + 1, r, d); //改3-4
} else {
LL x = read(); //查询单点
printf("%lld\n", query(1, x)); //以前缀和进行描述
}
}
return 0;
}

View File

@@ -0,0 +1 @@
<mxfile host="Electron" modified="2023-01-06T05:53:40.779Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="NHNIyiqg5EhPRGCV9j0k" version="20.3.0" type="device"><diagram id="8KDSlYUJhxJZUV9MUowc" name="第 1 页">7V1db+I4FP01PC7yd8LjtHQ/tB1pVpV2Z+YtIgaiDQkKYaDz69dpHCC+KWVmA3aaSH3AN1/knBP73hObjuj9av9bFqyXH9NQxiOCwv2ITkeEYEbIqPhD4XMZ8T1eBhZZFOqdjoGn6LvUQaSj2yiUm9qOeZrGebSuB2dpkshZXosFWZbu6rvN07h+1XWwkCDwNAtiGP0nCvOljmIxOW74XUaLpb60T7xywyqodtZ3slkGYbo7CdGHEb3P0jQvP6329zIuwKtwKY/79ZWthy+WySS/5ICPf/6R0cfscf7340PwdUtE9Nf3X/RZvgXxtn7Dm/y5gkCdRqGtGne7ZZTLp3UwK7bsFOEqtsxXsWph9THYrEsK5tFeqqvezdMk15SqL0fv9MVklsv9q3eBD9goUcl0JfPsWe2iD1CSKg/ReuJIw7s7ssN0aHnCSxULtB4WhzMfIVMfNGo/gCDpHIITzy0EadcQpD5yC0HWNQSZIG4hyLuGIGfcLQQFQBC5jSCm2C0EPYCgDxBUt5vXodrkWfqvvE/jNFORJE0KeOdRHBuhII4WiWrOFDxSxe8K8CKV6HzQG1ZRGMavcZOl2yQsmJiiOht40hIbxOhTOWRDNLBBr8WGD9iY9IcNgqhbbExg/wy7l/dLh8/doqMC/5QPOGC+Wz6o8B3jo6GOI/3hg3HsGB8NVSHtDx+cMsf4gDWm49mpcC07xQ1FJuuPpIVr+SmGJesUI9SBrKi1As6gRDQ8IrelBNbABSUdSIzac2ddowQW1YqSLtQO17J77VMCK+uCkh49JaZ/bJ8SWF5PMe7TWGIa0tYpqcY2g5IePSWmw22fElhkK0r6NJaYRYl9ShrqbECHU3Ud9uqlMRUQwtu+v4alMXSOnIKQGh2DfQhhaew4hBy5BiGsZl03aCbYMQhh9dkjfwaLer/KsO2hCVaevD90UMoNOphlOmDVKfpDB5u4RgesOL3+0CF8PHKqs6IN1SbqlQGAsTmcc8uUNFSbqFcGgDnrwT4lsNpUlPTJAGCg9LJNCaxeC0p69JSYnox9SmA1DNiQSfihWJxRwBoHm000q3NzHjW5j/LPxbYx160ves/i83R/2niuGom6t8+njS/6Wi+N40Evreqo8ovLEKwRMdhRN5dus5k8A4vONvMgW8j8XGZEmuk+oZM30FnFMhkHefSt/n2bONZX+JRG6k6OxkAllSoz8QyZlPepjzoqBZxIGG8CGTFOVAIBTvQiucNt/w8VQkOhbRVWisKWFFUth3JbUVgYE1nwTyoKGy8yGfJvqyjorwz92mH909sqpEO/1oYKoa3klArH3qkO0XjC35DiS+uTzCKFTJFeta1P2gl9UmIsyvvZXpIKNPa55wtGPUGEPzFsUcTGmBFMuSDUoxiJ26oX2j5XUy8lNf2OkcfPa1g1TB1a713ZpeplQ+/agj4ZNF1c6l1tZZp8UOFNVQh9pqupkJAf0KGLPSS+eIDngzjbEGfn5otwIwciDW9bbrtcHzpkcHGQUxASDzkGIbR34Nt1pyA0rXPb00XYYGecm1H45nDChlynleHEbT/DVsZNLs1qBhm2I0M4PcglGVrrDS/1H9jg7rYiwxv6Y13qDS81IAYZtiLDCo/arIpeLWSl3qTGgO1JFRwakx1go7Xpq8ZsYmx7viSHDp16QPo0E4wg01Sx/YQ0zQTr1bJi81dXDktorFFyic/lHB2t/QiOMX2V2P7FEA49M5hdvls6iJGc2adjMODOmeNv5/rD+5xWcv3BgDv3mstxGVJk9Gqmei6VITs/bwhfa96Qah5/Ar/c/fiPBOjDfw==</diagram></mxfile>

View File

@@ -0,0 +1,4 @@
5 2
1 2 3 4 5
1 2 4 1 2
2 3

View File

@@ -0,0 +1,7 @@
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4

View File

@@ -0,0 +1,88 @@
/*
题目:
将某区间每一个数加上k
求出某区间每一个数的和
题目总结:
区间修改,区间查询
*/
#include <iostream>
using namespace std;
#define LL long long
#define N 100010
//快读
LL read() {
LL x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int n, q, m;
LL tr[N << 2], tag[N << 2];
void build() {
for (m = 1; m <= n;) m <<= 1;
for (int i = 1; i <= n; i++) tr[m + i] = read();
for (int i = m - 1; i >= 1; i--) tr[i] = tr[i << 1] + tr[i << 1 | 1];
}
void add(int l, int r, int d) {
int lc = 0, rc = 0, c = 1;
// 开区间,从左右叶子端点出发,如果l和r不是兄弟节点,就一路向上
// lc 表示当前左端点走到的子树有多少个元素在修改区间内
// rc 表示当前右端点走到的子树有多少个元素在修改区间内
// c 当前层叶子节点个数
for (l = m + l - 1, r = m + r + 1; l ^ r ^ 1; l >>= 1, r >>= 1, c <<= 1) {
tr[l] += d * lc, tr[r] += d * rc; //自底向上,更新统计信息tr和
if (~l & 1) tag[l ^ 1] += d, tr[l ^ 1] += d * c, lc += c; // l % 2 == 0 左端点 是左儿子 右子树完整在区间内
if (r & 1) tag[r ^ 1] += d, tr[r ^ 1] += d * c, rc += c; // r % 2 == 1 右端点 是右儿子 左子树完整在区间内
}
//是兄弟节点时,需要一路向上到根进行推送
for (; l || r; l >>= 1, r >>= 1) tr[l] += d * lc, tr[r] += d * rc;
}
//区间查询
LL query(int l, int r) {
LL lc = 0, rc = 0, c = 1;
LL res = 0;
for (l = m + l - 1, r = m + r + 1; l ^ r ^ 1; l >>= 1, r >>= 1, c <<= 1) {
if (tag[l]) res += tag[l] * lc;
if (tag[r]) res += tag[r] * rc;
if (~l & 1) res += tr[l ^ 1], lc += c;
if (r & 1) res += tr[r ^ 1], rc += c;
}
for (; l || r; l >>= 1, r >>= 1) res += tag[l] * lc, res += tag[r] * rc;
return res;
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("P3372.in", "r", stdin);
#endif
n = read(), q = read();
build();
for (int i = 1; i <= q; i++) {
int op = read();
int l = read(), r = read();
if (op == 1) {
int c = read();
add(l, r, c);
} else
printf("%lld\n", query(l, r));
}
return 0;
}

View File

@@ -0,0 +1,81 @@
#include <iostream>
using namespace std;
// 第10个测试点 82ms
//快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
#define LL long long
const int N = 1e5 + 5;
int n, q, m;
LL tr[N << 2], tag[N << 2], sz[N << 2];
void pushup(int x) {
tr[x] = tr[x << 1] + tr[x << 1 | 1];
}
void build() {
for (m = 1; m <= n;) m <<= 1;
for (int i = 1; i <= n; i++) tr[i + m] = read();
for (int i = m; i <= (m << 1); ++i) sz[i] = 1;
for (int i = m - 1; i; i--) sz[i] = sz[i << 1] + sz[i << 1 | 1], pushup(i);
}
void addNode(int x, int v) {
tag[x] += v;
tr[x] += sz[x] * v;
}
void pushdown(int x) {
if (x ^ 1) pushdown(x >> 1);
if (tag[x]) addNode(x << 1, tag[x]), addNode(x << 1 | 1, tag[x]);
tag[x] = 0;
}
void add(int l, int r, LL v) {
pushdown((l += m - 1) >> 1), pushdown((r += m + 1) >> 1);
for (; l ^ r ^ 1; l >>= 1, r >>= 1) {
if (~l & 1) addNode(l ^ 1, v);
if (r & 1) addNode(r ^ 1, v);
pushup(l >> 1), pushup(r >> 1);
}
for (l >>= 1; l; l >>= 1) pushup(l);
}
LL query(int l, int r) {
LL res = 0;
pushdown((l += m - 1) >> 1), pushdown((r += m + 1) >> 1);
for (; l ^ r ^ 1; l >>= 1, r >>= 1) {
if (~l & 1) res += tr[l ^ 1];
if (r & 1) res += tr[r ^ 1];
}
return res;
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("P3372.in", "r", stdin);
#endif
n = read(), q = read();
build();
while (q--) {
int a, b, c, d;
a = read(), b = read(), c = read();
if (b > c) swap(b, c);
if (a == 1)
d = read(), add(b, c, d);
else
printf("%lld\n", query(b, c));
}
return 0;
}

View File

@@ -0,0 +1,93 @@
#include <iostream>
/*
第10个测试点 60ms/2.71MB
*/
using namespace std;
#define LL long long
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
const int N = 1e5 + 10;
int n, q, h, m;
LL sum[N << 1], tag[N << 1];
void build() {
for (h = 1; 1 << h < n;) h++;
for (int i = n + 1; i <= 2 * n; i++) sum[i] = read();
for (int i = n; i; i--) sum[i] = sum[i << 1] + sum[i << 1 | 1];
}
void add(int x, int val, int len) {
sum[x] += val * len;
if (x <= n) tag[x] += val;
}
void pushup(int x) {
int len = 1;
while (x > 1) len <<= 1, x >>= 1, sum[x] = sum[x << 1] + sum[x << 1 | 1] + tag[x] * len;
}
void pushdown(int x) {
for (int i = h, len = 1 << (h - 1); i; i--, len >>= 1) {
int y = x >> i;
if (tag[y]) {
add(y << 1, tag[y], len), add(y << 1 | 1, tag[y], len);
tag[y] = 0;
}
}
}
void modify(int l, int r, int val) {
l += n, r += n;
int yl = l, yr = r;
for (int len = 1; l <= r; l = (l + 1) >> 1, r = (r - 1) >> 1, len <<= 1) {
if (l & 1) add(l, val, len);
if (r & 1 ^ 1) add(r, val, len);
}
pushup(yl), pushup(yr);
}
LL query(int l, int r) {
l += n, r += n;
pushdown(l), pushdown(r);
LL res = 0;
for (; l <= r; l = (l + 1) >> 1, r = (r - 1) >> 1) {
if (l & 1) res += sum[l];
if (r & 1 ^ 1) res += sum[r];
}
return res;
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("P3372.in", "r", stdin);
#endif
n = read(), q = read();
build();
for (int i = 1; i <= q; i++) {
int op = read();
int l = read(), r = read();
if (op == 1) {
int c = read();
modify(l, r, c);
} else
printf("%lld\n", query(l, r));
}
return 0;
}

View File

@@ -0,0 +1,55 @@
#include <iostream>
using namespace std;
const int N = 1000010;
typedef long long LL;
int n, m; // n个元素,m次操作
int a[N]; //原始数组
LL tr1[N], tr2[N]; //① 保存基底数组为原数组差分数组的树状数组 ② i*b[i]的前缀和数组
//树状数组模板
#define lowbit(x) (x & -x)
void add(int x, int c) {
for (int i = x; i <= n; i += lowbit(i)) tr1[i] += c, tr2[i] += x * c;
}
LL sum(int x) {
LL res = 0;
for (int i = x; i; i -= lowbit(i)) res += (x + 1) * tr1[i] - tr2[i];
return res;
}
//快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int main() {
n = read(), m = read();
int x, y, d, op;
for (int i = 1; i <= n; i++) {
a[i] = read();
add(i, a[i] - a[i - 1]); //保存基底是差分数组的树状数组
}
while (m--) {
op = read(), x = read(), y = read();
if (op == 1) {
d = read();
add(x, d), add(y + 1, -d); //维护差分
} else //查询
printf("%lld\n", sum(y) - sum(x - 1));
}
return 0;
}

View File

@@ -0,0 +1,113 @@
#include <iostream>
using namespace std;
typedef long long LL;
#define ls u << 1
#define rs u << 1 | 1
const int N = 100010;
//快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int n, q;
struct Node {
int l, r;
LL sum, tag; //区间总和,修改的数值(懒标记)
} tr[N << 2];
//更新统计信息
void pushup(int u) {
tr[u].sum = tr[ls].sum + tr[rs].sum;
}
//父节点向子孙节点传递信息
void pushdown(int u) {
if (tr[u].tag) {
//更新儿子的tag信息,是为了更下一层的数据传递
// tag传递到子段,节段的sum和需要按 区间长度*root.tag 进行增加
tr[ls].tag += tr[u].tag;
tr[ls].sum += LL(tr[ls].r - tr[ls].l + 1) * tr[u].tag;
tr[rs].tag += tr[u].tag;
tr[rs].sum += LL(tr[rs].r - tr[rs].l + 1) * tr[u].tag;
//别忘了清除懒标记
tr[u].tag = 0;
}
}
//构建
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r; //标记范围
if (l == r) { //叶子
tr[u].sum = read(); //区间内只有一个元素l(r),区间和为read(),不需要记录向下的传递tag
return;
}
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid + 1, r); //左右儿子构建
pushup(u); //通过左右儿子构建后,向祖先节点反馈统计信息变化
}
//以u为根,在区间[l,r]之间全都增加d
void add(int u, int l, int r, int d) {
if (tr[u].l >= l && tr[u].r <= r) {
tr[u].sum += LL(tr[u].r - tr[u].l + 1) * d; //总和增加
tr[u].tag += d; //懒标记+d
return;
}
pushdown(u); // 如果自己身上有旧的tag数值,在递归前需要将原tag值pushdown到子孙节点去
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) add(ls, l, r, d); //与左区间有交集
if (r > mid) add(rs, l, r, d); //与右区间有交集
pushup(u); //将结果的变更更新到祖先节点
}
//关键的查询操作
LL query(int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
/*懒标记的思想:在更新时只标识不修改,有查询时现用现改的思想。这么做的目的当然是怕有多组修改,
就需要修改多次,而且路径太长不划算。如果只标识不修改,直接返回,当然快了。比如修改了3次后查询一次,
也只是真正修改了一次,降低了修改次数。
*/
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL res = 0;
if (l <= mid) res = query(ls, l, r);
if (r > mid) res += query(rs, l, r);
//左查+ 右查 = 总和
return res;
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("P3372.in", "r", stdin);
#endif
n = read(), q = read();
//构建线段树
build(1, 1, n);
while (q--) {
int a, b, c, d;
a = read(), b = read(), c = read();
if (b > c) swap(b, c);
if (a == 1)
d = read(), add(1, b, c, d);
else
printf("%lld\n", query(1, b, c));
}
return 0;
}

View File

@@ -0,0 +1,25 @@
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
//快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int main() {
int l = 1, r = 3;
int mid = (l + r) >> 1;
printf("%d\n", mid);
return 0;
}

View File

@@ -0,0 +1,597 @@
## $zkw$ 线段树
[参考博文I](https://codeforces.com/blog/entry/18051) 这篇英文文献还是要看一下的,有意思~
[参考博文II](https://blog.csdn.net/yanweiqi1754989931/article/details/117575178)
俗称 **重口味** ,与 $KMP$ 类似,咳咳...
只要没标记,我都写 $zkw$ 线段树。。 只要有标记,我都写递归版线段树。。
### 一、$ZKW$线段树简介
$ZKW$线段树是由清华大学姚班大佬 **张昆玮** 所创立的一种线段树储存结构,由于其基于非递归的实现方式以及精简的代码和较高的效率而闻名。甚至,$ZKW$线段树能够可持久化。
我们从算法的角度对基础线段树进行分析:其实线段树算法本身的本质仍是统计。因此我们可以从统计的角度入手对线段树进行分析:线段树是将一个个数轴划分为区间进行处理的,因此我们面对的往往是一系列的离散量,这导致了我们在使用时的线段树单纯的退化为一棵"点树"(即最底层的线段树只包含一个点)。基于这一点可以入手对线段树进行优化。
### 二、$ZKW$线段树的构造原理
首先,我们忽略线段树中的数据,从线段树的框架结构入手进行分析:如图所示是一颗采用堆式储存的基本线段树:
<center><img src='https://img-blog.csdnimg.cn/20210604234646100.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lhbndlaXFpMTc1NDk4OTkzMQ==,size_16,color_FFFFFF,t_70#pic_center'></center>
我们将节点编号转换为二进制:
<center><img src='https://img-blog.csdnimg.cn/20210604234653819.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lhbndlaXFpMTc1NDk4OTkzMQ==,size_16,color_FFFFFF,t_70#pic_center'></center>
观察转为二进制后的结点规律:在基础线段树的学习中,我们知道对于任意结点$x$,其左子节点为$x<<1$,右子节点为$x<<1 ∣ 1$。这个规律是我们从根结点出发向叶节点寻找的规律。那么现在我们换个思路:从叶结点出发向根结点寻找规律:
* 当前结点的父节点一定是当前的结点右移一位(舍弃低位)得到的
* 当前结点的左子节点为$x<<1$,右子节点为$x<<1 ∣ 1$
* 每一层结点按照顺序排列,第$n$层有$2^{n-1}$个节点
* 最后一层的结点个数 = 值域
因为最后一层的结点个数=值域,假设给定数组$a[n]$,含有元素$a[1] \sim a[n]$。
我们约定,无论元素的个数是否达到$2^n$,最后一层的空间都开到$2^n$,无数据的叶节点空置即可。
### 三、$ZKW$线段树基本操作
#### 1.建树操作
```cpp {.line-numbers}
void build(int n){
for(m = 1; m <= n;) m <<= 1;
for (int i = m + 1; i <= m + n; ++i) op_array[i] = read();
for (int i = m - 1; i; --i) operation(),
}
```
- 如果维护区间和,那么$op\_array[] \rightarrow sum[],operation$
```cpp {.line-numbers}
sum[i] = sum[i << 1] + sum[i << 1 | 1];
```
- 如果维护区间最小值,那么$op\_array[] \rightarrow minn[],operation$
```cpp {.line-numbers}
minn[i] = min(minn[i << 1], minn[i << 1 | 1]); //不支持修改操作
```
```cpp {.line-numbers}
minn[i] = min(minn[i << 1], minn[i << 1 | 1]),
minn[i << 1] -= minn[i], minn[i << 1 | 1] -= minn[i];
```
- 如果维护区间最大值,那么$op\_array[] \rightarrow maxx[],operation$
```cpp {.line-numbers}
maxx[i] = max(maxx[i << 1], maxx[i << 1 | 1]); //不支持修改操作
```
```cpp {.line-numbers}
maxx[i] = max(maxx[i << 1], maxx[i << 1 | 1]),
maxx[i << 1] -= maxx[i], maxx[i << 1 | 1] -= maxx[i];
```
#### 2.单点查询
这个操作是相对容易理解的,就是一个从叶子结点开始,不断向父节点走,同时累加沿路的权值的过程。
```cpp {.line-numbers}
int query(int x){
int ans = 0;
for (x += m; x; x >>= 1) ans += minn[s];
return ans;
}
```
#### 3.单点修改
单点修改的思路非常简单,只需要修改当前结点并更新父节点即可。
```cpp {.line-numbers}
void update(int x,int v){
op_array[x = m + x] += v;
while(x) operation();
}
```
- 如果维护区间和,那么$op\_array[] \rightarrow sum[],operation:$
```cpp {.line-numbers}
sum[i] = a[i << 1] + a[i << 1 | 1];
```
```cpp {.line-numbers}
//如果单纯维护区间和,那么可以压行:
void update(int p, int k){ for (p += m; p; p >>= 1) sum[p] += k; }
```
- 如果维护区间最小值,那么$op\_array[] \rightarrow minn[],operation$
```cpp {.line-numbers}
minn[i] = min(minn[i << 1], minn[i << 1 | 1]),
minn[i << 1] -= minn[i], minn[i << 1 | 1] -= minn[i];
```
- 如果维护区间最大值,那么$op\_array[] \rightarrow maxx[],operation$
```cpp {.line-numbers}
maxx[i] = max(maxx[i << 1], maxx[i << 1 | 1]),
maxx[i << 1] -= maxx[i], maxx[i << 1 | 1] -= maxx[i];
```
#### 4.区间查询
如何进行区间查询?我们继续二进制表示入手,寻找查询的规律。
在实际的查询中,我们采取扩增左右区间端点的方式进行查询,即:将闭区间转换为开区间查询。
我们以下图为例:假设要查询的区间为$[1,2]$,那么首先转换为开区间$(0,3)$,我们可以发现变为开区间之后,$0$的兄弟结点必在区间之内,$3$的兄弟结点​必在区间内;根据这个规律我们可以总结:
对于待查区间$[l,r]$:
- 如果$l$是左儿子,则其兄弟结点必位于区间之内;
- 如果$r$是右儿子,则其兄弟结点必位于区间之内;
- 查询的终止条件:两个结点同为兄弟;
- 以上结论,对于任意层的结点均成立。
我们通过例子来模拟这个过程:
<center><img src='https://img-blog.csdnimg.cn/20210604234719972.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lhbndlaXFpMTc1NDk4OTkzMQ==,size_16,color_FFFFFF,t_70#pic_center'></center>
在如图所示的$ZKW$线段树中,假设我们要查询区间$[1,4]$,那么步骤如下:
- 闭区间改开区间,$[1,4]$改为查询$(0,5)$,扩增至$(M + 0, M + 5) = (8, 13)$;
- 判断:左端点$D[1000_B]$是左儿子,那么其兄弟$D[1001_B]$必位于区间内,累加$ans + = D[1001_B]$
判断:右端点$D[1101_B]$是右儿子,那么其兄弟$D[1100_B]$必位于区间内,累加$ans + = D[ 1100_B]$
- 缩小区间(向根结点缩):$l > > = 1 → 1000 >> 1 = 0100 , r > > = 1 → 1101 > > 1 = 0110 $
- 判断:左端点$D[0100_B]$是左儿子,那么其兄弟$D[0101_B]$必位于区间内,累加$an s + = D[0101_B]$
判断:右端点$D[0110_B]$是左儿子,不做操作;
- 缩小区间(向根结点缩):$l >> = 1 → 0100 > > 1 = 0010 , r > > = 1 → 0110 > > 1 = 0011$
- 此时$l$和$r$同为兄弟,因此终止查询。
我们可以总结出区间查询的步骤:
* 闭区间改开区间$[l, r] → ( l + M − 1 , r + M + 1 )$
* 判断当前区间左端点是否是左儿子,如果是,则向累加器中累加兄弟结点;
判断当前区间右端点是否为右儿子,如果是,则向累加器中累加兄弟结点;
* 端点变量处理操作:$l > > = 1 , r > > = 1$
* 循环执行$2 − 3$的步骤,直到$l$和$r$同为兄弟结点(此时不终止会导致重复计算)
如何判断是否为左子节点?我们很容易观察到左右子节点共同的特征:左子节点最低位为$0$ ,右子节点最低位为$1$,那么我们可以通过以下操作的真值判断左右子节点
- 判断左子节点:`∼l & 1`
`l % 2 == 0`的意思
- 判断右子节点:`r & 1`
` r % 2 == 1`的意思
对于取兄弟结点的值则可以通过与$1$异或求得:
- 左子节点求兄弟结点:`l xor 1`
- 右子节点求兄弟结点:`r xor 1`
**延绵的山峰 无修改维护最值**
```cpp {.line-numbers}
/*
延绵的山峰
★★☆ 输入文件:climb.in 输出文件:climb.out 简单对比
时间限制:1 s 内存限制:512 MB
问题描述
有一座延绵不断、跌宕起伏的山,最低处海拔为0,最高处海拔不超过8848米,从这座山的一端走到另一端的过程中,
每走1米海拔就升高或降低1米。有Q个登山队计划在这座山的不同区段登山,当他们攀到各自区段的最高峰时,就会插上队旗。
请你写一个程序找出他们插旗的高度。
输入文件
第1行,一个整数N(N<=10^6),表示山两端的跨度。
接下来N+1行,每行一个非负整数Hi,表示该位置的海拔高度,其中H0=Hn=0。
然后是一个正整数Q(Q<=7000),表示登山队的数量。
接下来Q行,每行两个数Ai, Bi,表示第i个登山队攀爬的区段[Ai,Bi],其中0<=Ai<=Bi<=N。
输出文件
Q行,每行为一个整数,表示第i个登山队插旗的高度。
样例输入
10
0
1
2
3
2
3
4
3
2
1
0
5
0 10
2 4
3 7
7 9
8 8
样例输出
4
3
4
3
2
*/
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 100010;
// 快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int d[N << 2], m;
//构建zkw线段树
void build(int n) {
for (m = 1; m < n;) m <<= 1; // 强行开够(大于n)方便二进制访问叶节点
for (int i = 1; i <= n; i++) d[m + i] = read(); //对zkw的叶子节点赋值
for (int i = m; i; i--) d[i] = max(d[i << 1], d[i << 1 | 1]); //待所有叶子节点有值后,统一执行一次向上数据统计信息汇总
}
//区间查询最大值
int query(int l, int r) {
int ans = 0;
for (l = l + m - 1, r = r + m + 1; l ^ r ^ 1; l >>= 1, r >>= 1) {
if (~l & 1) ans = max(d[l ^ 1], ans);
if (r & 1) ans = max(d[r ^ 1], ans);
}
return ans;
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("Cogs58.in", "r", stdin);
#endif
int n, q, l, r;
/*
第1行,一个整数N(N<=10^6),表示山两端的跨度。
接下来N+1行,每行一个非负整数Hi,表示该位置的海拔高度,其中H0=Hn=0。
*/
n = read(), n++; // H0~Hn 共n+1行,而且,题目告诉我们H0=Hn=0,会在输入数据时给出,所以,一共是n+1点
//构建
build(n);
q = read();
for (int i = 1; i <= q; i++) {
l = read(), r = read();
printf("%d\n", query(l + 1, r + 1));
}
return 0;
}
```
[$I$ $Hate$ $It$ $HDU$ - $1754$ 单点更新,区间查询](http://acm.hdu.edu.cn/showproblem.php?pid=1754)
```cpp {.line-numbers}
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 2e5 + 1000;
// 快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
// zkw线段树
int d[N << 2];
int n; // n个叶子节点
int q; // q个操作,区间查询或单点修改
int m; // m是指所有父节点们的个数
void pushup(int x) {
for (int i = (m + x) >> 1; i; i >>= 1) d[i] = max(d[i << 1], d[i << 1 | 1]);
}
//单点修改并psuhup
void modify(int x, int c) {
d[m + x] = c;
pushup(x);
}
//构建重口味树
void built() {
memset(d, 0, sizeof d);
for (m = 1; m <= n + 1;) m <<= 1; // 强行开够(大于n)方便二进制访问叶节点
for (int i = 1; i <= n; i++) d[m + i] = read(); //初始值
for (int i = m; i; i--) d[i] = max(d[i << 1], d[i << 1 | 1]); //待所有叶子节点有值后,统一执行一次向上数据统计信息汇总
}
//区间查询
int query(int l, int r) {
int ans = -1;
// 闭区间改为开区间:l=l+m-1 : 将查询区间改为l-1, r=r+m+1 : 将查询区间改为r+1
// l^r^1 : 相当于判断l与r是否是兄弟节点
for (l = l + m - 1, r = r + m + 1; l ^ r ^ 1; l >>= 1, r >>= 1) {
if (~l & 1) ans = max(ans, d[l ^ 1]); // l % 2 == 0 左端点 是左儿子 max(右子树),想像一下题解中0号端点(哨兵),肯定是%2=0
if (r & 1) ans = max(ans, d[r ^ 1]); // r % 2 == 1 右端点 是右儿子 max(左子树),想像一下题解中1号端点,肯定是%2=1
}
return ans;
}
//方法重载,单点查询 [本题没有用到,用来做模板的]
int query(int x) {
return d[m + x]; //直接返回结果的zkw线段树节点值
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("HDU1754.in", "r", stdin);
#endif
char op[2];
while (~scanf("%d%d", &n, &q)) {
//构建zkw树
built();
while (q--) {
scanf("%s", op);
int l = read(), r = read();
if (op[0] == 'Q')
printf("%d\n", query(l, r));
else
modify(l, r);
}
}
return 0;
}
```
[$HDU$ $1166$ 敌兵布阵 单点增加,区间查询](http://acm.hdu.edu.cn/showproblem.php?pid=1166)
```cpp {.line-numbers}
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
//快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
const int N = 50000 + 10;
int m = 1, n;
int d[N << 2];
//区间查询
int query(int l, int r) {
int ans = 0;
for (l = l + m - 1, r = r + m + 1; l ^ r ^ 1; l >>= 1, r >>= 1) {
if (~l & 1) ans += d[l ^ 1];
if (r & 1) ans += d[r ^ 1];
}
return ans;
}
//向上更新统计信息
void pushup(int x) {
for (int i = (m + x) >> 1; i; i >>= 1) d[i] = d[i << 1] + d[i << 1 | 1];
}
//单点增加
void add(int x, int c) {
d[m + x] += c;
pushup(x);
}
void build(int n) {
memset(d, 0, sizeof d);
for (m = 1; m < n;) m <<= 1; // 强行开够(大于n)方便二进制访问叶节点
for (int i = 1; i <= n; i++) d[m + i] = read(); //初始值
for (int i = m; i; i--) d[i] = d[i << 1] + d[i << 1 | 1]; //待所有叶子节点有值后,统一执行一次向上数据统计信息汇总
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("HDU1166.in", "r", stdin);
#endif
int k = read();
for (int q = 1; q <= k; q++) {
n = read();
//构建zkw线段树
build(n);
printf("Case %d:\n", q);
char op[10];
while (scanf("%s", op)) {
if (op[0] == 'E') break;
int l = read(), r = read();
if (op[0] == 'Q')
printf("%d\n", query(l, r));
else if (op[0] == 'A')
add(l, r);
else if (op[0] == 'S')
add(l, -r);
}
}
return 0;
}
```
#### 5.区间修改
[参考题解](https://www.cnblogs.com/Parsnip/p/10785894.html)
在普通的线段树中,一般用 $lazy$ $tag$ 来解决这个问题,$zkw$线段树同样可以,但从上向下操作、下放 $lazy$ $tag$ 等操作并不优雅,**常数大**。
可以采用 **标记永久化**,随用随查,按$zkw$的说法——永久化的标记就是值。
考虑上文提到的区间操作的过程,自下向上走的过程中,根据遇到的标记来计算贡献。
对于一个节点,若修改操作对节点所代表的整个区间产生影响,显然我们可以直接对该节点进行标记,而非逐层递归修改。
那么,在自底向上的线段树中,我们可以不下传标记,而是在每一次查询时,统计累加一路上所有标记对答案产生的影响,这种标记思想被称为**标记永久化**。
**区间修改**
关于标记永久化,我们进行定义:$add[i]$代表线段树中$i$号节点的关键值已经进行修改,但是其所有子节点均有一个值为$add[i]$的增量未进行处理。
设置两个开区间指针$l,r$,并同时向上遍历。同时,我们维护三个变量$lc,rc,c$,分别代表:
- 当前左端点走到的子树有多少个元素在修改区间内
- 当前右端点走到的子树有多少个元素在修改区间内
- 当前层叶子节点个数
利用上述变量和$add$标记的定义,沿路更新$add$标记和原线段树即可,当然,对于$l,r$成为兄弟后,我们还须将$add$标记一直上推到根节点。
**区间求和**
有了$add$标记,我们就很容易求得区间的和了: 将闭区间转换为开区间,然后向上遍历,
同样维护$lc,rc,c$,然后利用$add$标记进行累加,再加上原来的区间和,就能得到答案。
**[例题P3372 【模板】线段树 1](https://www.luogu.org/problemnew/show/P3372)**
```cpp {.line-numbers}
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define LL long long
#define N 100010
//对于区间修改 区间求和的zkw线段树,最重要的思想就是标记永久化的思想
//快读
LL read() {
LL x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int n, q, m;
LL sum[N << 2], add[N << 2];
void build() {
for (m = 1; m <= n;) m <<= 1;
for (int i = 1; i <= n; i++) sum[m + i] = read();
for (int i = m - 1; i >= 1; i--) sum[i] = sum[i << 1] + sum[i << 1 | 1];
}
void modify(int l, int r, int d) {
int lc = 0, rc = 0, c = 1;
// 开区间,从左右叶子端点出发,如果l和r不是兄弟节点,就一路向上
// lc 表示当前左端点走到的子树有多少个元素在修改区间内
// rc 表示当前右端点走到的子树有多少个元素在修改区间内
// c 当前层叶子节点个数
for (l = m + l - 1, r = m + r + 1; l ^ r ^ 1; l >>= 1, r >>= 1, c <<= 1) {
sum[l] += d * lc, sum[r] += d * rc; //自底向上,更新统计信息sum和
if (~l & 1) add[l ^ 1] += d, sum[l ^ 1] += d * c, lc += c; // l % 2 == 0 左端点 是左儿子 右子树完整在区间内
if (r & 1) add[r ^ 1] += d, sum[r ^ 1] += d * c, rc += c; // r % 2 == 1 右端点 是右儿子 左子树完整在区间内
}
//是兄弟节点时,需要一路向上到根进行推送
for (; l || r; l >>= 1, r >>= 1) sum[l] += d * lc, sum[r] += d * rc;
}
//区间查询
LL query(int l, int r) {
LL lc = 0, rc = 0, c = 1;
LL res = 0;
for (l = m + l - 1, r = m + r + 1; l ^ r ^ 1; l >>= 1, r >>= 1, c <<= 1) {
if (add[l]) res += add[l] * lc;
if (add[r]) res += add[r] * rc;
if (~l & 1) res += sum[l ^ 1], lc += c;
if (r & 1) res += sum[r ^ 1], rc += c;
}
for (; l || r; l >>= 1, r >>= 1) res += add[l] * lc, res += add[r] * rc;
return res;
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("P3372.in", "r", stdin);
#endif
n = read(), q = read();
build();
for (int i = 1; i <= q; i++) {
int op = read();
int l = read(), r = read();
if (op == 1) {
int c = read();
modify(l, r, c);
} else
printf("%lld\n", query(l, r));
}
return 0;
}
```
### 五、总结
简单总结一下,上面也说过,这东西除了卡常就没什么用,还不见得有原版线段树好写
实战中一般不会有人特意放 $zkw$ 过然后卡递归线段树
感觉实用性甚至不如什么猫树
不过思想还可以,值得学学
运行情况对比: $P3372$ 【模板】线段树 1
版本1:zkw线段树+标记永久化 第10个测试点 44ms/3.75MB
版本2:zkw线段树+pushup+pushdown 第10个测试点 85ms/5.87MB
版本3:zkw线段树+cf_zkw变异版本,内存2*N 第10个测试点 60ms/2.71MB
版本4:树状数组+差分 第10个测试点 31ms/2.36MB
版本5:递归版本普通线段树 第10个测试点 105ms/8.33MB
总结:
(1) 普通线段树最牛B,没有之一,对,你没有看错,因为大家基本上是一个级别的,
比最快的仅仅慢3倍而已,我过不了,你估计也过不了,因为出题人一般都是卡一个复杂度的,卡常数不容易。
ZKW这个东东,没有必要再花时间研究了,因为快也快不到哪里去,还徒增学习的时间,不划算。
(2) 有哪些问题是线段树可以解决但树状数组解决不了的?
树状数组可以维护前缀和(或后缀和),维护不了一般的区间和,而线段树可以轻松维护。
这里的“和”是指一种满足结合律的运算的累算,例如普通加/乘法、最大/小值、矩阵乘法、
最大公约数等等。可以求差的运算也可以用树状数组维护前缀和来计算区间和,
比如普通加法的区间和是两个前缀和的差,
可以很容易支持 区间修改+单点询问 或 单点修改+区间询问,但没法很好地同时支持
区间修改+区间询问(有时也可以强行懒惰更新)。
不能求差的运算,树状数组就维护不了区间和了,比如区间最大/小值、区间最大公约数。
其实树状数组只是线段树的一个特殊形式,因为不需要询问前缀以外的子区间,需要维护的信息更少,
把线段树上的一些不需要的节点删去了。

View File

@@ -0,0 +1,82 @@
//博文:https://codeforces.com/blog/entry/18051
// zkw 单点修改 区间查询 例子
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
//快读
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
const int N = 1e5 + 10; // limit for array size
int n; // array size
int t[N << 1]; // 2倍内存
void build() { //构建zkw线段树
for (int i = 0; i < n; i++) t[n + i] = read(); //数据从下标n开始读
for (int i = n - 1; i; i--) t[i] = t[i << 1] + t[i << 1 | 1]; //父亲结点们更新统计信息sum和
}
//单点修改
void modify(int p, int value) { // set value at position p
//① 将指定位置p的对应叶子结点位置修改为value
//② 修改指定结点的父亲们,p>>1就是不断的向上找父亲节点,不断向上更新sum和
for (t[p += n] = value; p > 1; p >>= 1) t[p >> 1] = t[p] + t[p ^ 1];
}
//区间查询
int query(int l, int r) { // sum on interval [l, r)
int res = 0;
for (l += n, r += n; l < r; l >>= 1, r >>= 1) {
//① 如果l是奇数,l是父亲的右孩子(看一下上面的图,确实是右孩子)
// (1) 此时,只有它自己有用,它的父亲不包含在查询区间内。自己加上,把略过即可
// (2) 怎么略过呢?巧妙的办法:本轮次不加自己父亲,下软移动到右边的兄弟节点的父亲上去! l= (l+1) >> 1
//② 如果l是偶数,那么l就是父亲的左儿子,整个区间完整命中,直接研究它父亲的sum即可, l>>=1
if (l & 1) res += t[l++];
//与上面代码对称的写法
if (r & 1) res += t[--r];
}
return res;
}
/*
输入样例:
15
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
输出样例:
52
手工验证
[3,11)
3+4+5+6+7+8+9+10=52 正确
*/
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("zkw_Study_1.in", "r", stdin);
#endif
n = read();
build();
//单点修改的例子,后面查询没用上这个修改
modify(0, 1);
//区间查询,左闭右开
printf("%d\n", query(3, 11));
return 0;
}

View File

@@ -0,0 +1,2 @@
15
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

View File

@@ -0,0 +1,66 @@
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
const int N = 1e5 + 10;
int n;
int t[N << 1];
void build() {
for (int i = 0; i < n; i++) t[n + i] = read();
for (int i = n - 1; i; i--) t[i] = t[i << 1] + t[i << 1 | 1];
}
void modify(int l, int r, int value) {
for (l += n, r += n; l < r; l >>= 1, r >>= 1) {
if (l & 1) t[l++] += value;
if (r & 1) t[--r] += value;
}
}
//区间查询
int query(int l, int r) { // sum on interval [l, r)
int res = 0;
for (l += n, r += n; l < r; l >>= 1, r >>= 1) {
//① 如果l是奇数,l是父亲的右孩子(看一下上面的图,确实是右孩子)
// (1) 此时,只有它自己有用,它的父亲不包含在查询区间内。自己加上,把略过即可
// (2) 怎么略过呢?巧妙的办法:本轮次不加自己父亲,下软移动到右边的兄弟节点的父亲上去! l= (l+1) >> 1
//② 如果l是偶数,那么l就是父亲的左儿子,整个区间完整命中,直接研究它父亲的sum即可, l>>=1
if (l & 1) res += t[l++];
//与上面代码对称的写法
if (r & 1) res += t[--r];
}
return res;
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("zkw_Study_2.in", "r", stdin);
#endif
n = read();
build();
modify(0, 1, 1); //修改[0,1)也是就是修改下标0的值为1
for (int i = n; i < 2 * n; i++) printf("%d ", t[i]);
puts("");
printf("%d\n", query(1, 3)); //下标从0开始,查询[1,3)=3
return 0;
//感悟:这东西区间修改,区间查询就OK了,没有什么单点修改,单点查询,直接上区间修改、区间查询不就行了吗?
}

View File

@@ -0,0 +1,2 @@
15
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

View File

@@ -0,0 +1,91 @@
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
const int N = 1e5 + 10;
int n, h, m;
LL sum[N << 1], tag[N << 1];
void build() {
for (h = 1; 1 << h < n;) h++;
for (int i = n + 1; i <= 2 * n; i++) sum[i] = read();
for (int i = n; i; i--) sum[i] = sum[i << 1] + sum[i << 1 | 1];
}
void add(int x, int val, int len) {
sum[x] += val * len;
if (x <= n) tag[x] += val;
}
void pushup(int x) {
int len = 1;
while (x > 1) len <<= 1, x >>= 1, sum[x] = sum[x << 1] + sum[x << 1 | 1] + tag[x] * len;
}
void pushdown(int x) {
for (int i = h, len = 1 << (h - 1); i; i--, len >>= 1) {
int y = x >> i;
if (tag[y]) {
add(y << 1, tag[y], len), add(y << 1 | 1, tag[y], len);
tag[y] = 0;
}
}
}
void modify(int l, int r, int val) {
l += n, r += n;
int yl = l, yr = r;
for (int len = 1; l <= r; l = (l + 1) >> 1, r = (r - 1) >> 1, len <<= 1) {
if (l & 1) add(l, val, len);
if (r & 1 ^ 1) add(r, val, len);
}
pushup(yl), pushup(yr);
}
LL query(int l, int r) {
l += n, r += n;
pushdown(l), pushdown(r);
LL res = 0;
for (; l <= r; l = (l + 1) >> 1, r = (r - 1) >> 1) {
if (l & 1) res += sum[l];
if (r & 1 ^ 1) res += sum[r];
}
return res;
}
int main() {
n = read(), m = read();
build();
int x, y, k;
char op[2];
while (m--) {
scanf("%s", op);
x = read(), y = read();
if (op[0] == 'C') {
k = read();
modify(x, y, k);
} else
printf("%lld\n", query(x, y));
}
return 0;
}