'commit'
This commit is contained in:
102
TangDou/AcWing/SegmentTree/ZKW/243.cpp
Normal file
102
TangDou/AcWing/SegmentTree/ZKW/243.cpp
Normal 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;
|
||||
}
|
||||
7
TangDou/AcWing/SegmentTree/ZKW/243.in
Normal file
7
TangDou/AcWing/SegmentTree/ZKW/243.in
Normal 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
|
||||
107
TangDou/AcWing/SegmentTree/ZKW/Cogs58.cpp
Normal file
107
TangDou/AcWing/SegmentTree/ZKW/Cogs58.cpp
Normal 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;
|
||||
}
|
||||
18
TangDou/AcWing/SegmentTree/ZKW/Cogs58.in
Normal file
18
TangDou/AcWing/SegmentTree/ZKW/Cogs58.in
Normal 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
|
||||
77
TangDou/AcWing/SegmentTree/ZKW/HDU1166.cpp
Normal file
77
TangDou/AcWing/SegmentTree/ZKW/HDU1166.cpp
Normal 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;
|
||||
}
|
||||
10
TangDou/AcWing/SegmentTree/ZKW/HDU1166.in
Normal file
10
TangDou/AcWing/SegmentTree/ZKW/HDU1166.in
Normal 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
|
||||
79
TangDou/AcWing/SegmentTree/ZKW/HDU1754.cpp
Normal file
79
TangDou/AcWing/SegmentTree/ZKW/HDU1754.cpp
Normal 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;
|
||||
}
|
||||
8
TangDou/AcWing/SegmentTree/ZKW/HDU1754.in
Normal file
8
TangDou/AcWing/SegmentTree/ZKW/HDU1754.in
Normal 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
|
||||
131
TangDou/AcWing/SegmentTree/ZKW/HDU1754.md
Normal file
131
TangDou/AcWing/SegmentTree/ZKW/HDU1754.md
Normal 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;
|
||||
}
|
||||
```
|
||||
110
TangDou/AcWing/SegmentTree/ZKW/P1438.cpp
Normal file
110
TangDou/AcWing/SegmentTree/ZKW/P1438.cpp
Normal 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;
|
||||
}
|
||||
1
TangDou/AcWing/SegmentTree/ZKW/P1438.drawio
Normal file
1
TangDou/AcWing/SegmentTree/ZKW/P1438.drawio
Normal 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>
|
||||
4
TangDou/AcWing/SegmentTree/ZKW/P1438.in
Normal file
4
TangDou/AcWing/SegmentTree/ZKW/P1438.in
Normal file
@@ -0,0 +1,4 @@
|
||||
5 2
|
||||
1 2 3 4 5
|
||||
1 2 4 1 2
|
||||
2 3
|
||||
7
TangDou/AcWing/SegmentTree/ZKW/P3372.in
Normal file
7
TangDou/AcWing/SegmentTree/ZKW/P3372.in
Normal 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
|
||||
88
TangDou/AcWing/SegmentTree/ZKW/P3372_1.cpp
Normal file
88
TangDou/AcWing/SegmentTree/ZKW/P3372_1.cpp
Normal 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;
|
||||
}
|
||||
81
TangDou/AcWing/SegmentTree/ZKW/P3372_2.cpp
Normal file
81
TangDou/AcWing/SegmentTree/ZKW/P3372_2.cpp
Normal 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;
|
||||
}
|
||||
93
TangDou/AcWing/SegmentTree/ZKW/P3372_3.cpp
Normal file
93
TangDou/AcWing/SegmentTree/ZKW/P3372_3.cpp
Normal 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;
|
||||
}
|
||||
55
TangDou/AcWing/SegmentTree/ZKW/P3372_4.cpp
Normal file
55
TangDou/AcWing/SegmentTree/ZKW/P3372_4.cpp
Normal 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;
|
||||
}
|
||||
113
TangDou/AcWing/SegmentTree/ZKW/P3372_5.cpp
Normal file
113
TangDou/AcWing/SegmentTree/ZKW/P3372_5.cpp
Normal 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;
|
||||
}
|
||||
25
TangDou/AcWing/SegmentTree/ZKW/Test.cpp
Normal file
25
TangDou/AcWing/SegmentTree/ZKW/Test.cpp
Normal 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;
|
||||
}
|
||||
597
TangDou/AcWing/SegmentTree/ZKW/ZKW线段树.md
Normal file
597
TangDou/AcWing/SegmentTree/ZKW/ZKW线段树.md
Normal 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) 有哪些问题是线段树可以解决但树状数组解决不了的?
|
||||
树状数组可以维护前缀和(或后缀和),维护不了一般的区间和,而线段树可以轻松维护。
|
||||
这里的“和”是指一种满足结合律的运算的累算,例如普通加/乘法、最大/小值、矩阵乘法、
|
||||
最大公约数等等。可以求差的运算也可以用树状数组维护前缀和来计算区间和,
|
||||
比如普通加法的区间和是两个前缀和的差,
|
||||
可以很容易支持 区间修改+单点询问 或 单点修改+区间询问,但没法很好地同时支持
|
||||
区间修改+区间询问(有时也可以强行懒惰更新)。
|
||||
|
||||
不能求差的运算,树状数组就维护不了区间和了,比如区间最大/小值、区间最大公约数。
|
||||
其实树状数组只是线段树的一个特殊形式,因为不需要询问前缀以外的子区间,需要维护的信息更少,
|
||||
把线段树上的一些不需要的节点删去了。
|
||||
82
TangDou/AcWing/SegmentTree/ZKW/zkw_Study_1.cpp
Normal file
82
TangDou/AcWing/SegmentTree/ZKW/zkw_Study_1.cpp
Normal 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;
|
||||
}
|
||||
2
TangDou/AcWing/SegmentTree/ZKW/zkw_Study_1.in
Normal file
2
TangDou/AcWing/SegmentTree/ZKW/zkw_Study_1.in
Normal file
@@ -0,0 +1,2 @@
|
||||
15
|
||||
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
||||
66
TangDou/AcWing/SegmentTree/ZKW/zkw_Study_2.cpp
Normal file
66
TangDou/AcWing/SegmentTree/ZKW/zkw_Study_2.cpp
Normal 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了,没有什么单点修改,单点查询,直接上区间修改、区间查询不就行了吗?
|
||||
}
|
||||
2
TangDou/AcWing/SegmentTree/ZKW/zkw_Study_2.in
Normal file
2
TangDou/AcWing/SegmentTree/ZKW/zkw_Study_2.in
Normal file
@@ -0,0 +1,2 @@
|
||||
15
|
||||
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
||||
91
TangDou/AcWing/SegmentTree/ZKW/zkw_Study_3.cpp
Normal file
91
TangDou/AcWing/SegmentTree/ZKW/zkw_Study_3.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user