浅谈线段树及其应用

news/2025/11/11 10:57:08/文章来源:https://www.cnblogs.com/Ahui2667d/p/19209418

———————————————————————————————————————————————————————————————————————————————————————————

概要

本文介绍了线段树的基本原理和实现,举了一些典型的可以用线段树解决的问题并进行分析和解答。

—BY Ahui2667d(张云辉)

本文代码适用于c++

本文代码中的通用宏定义

#define ll long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)

线段树的简单理解及实现

问题的引入:

下面考虑这样一个问题:给定一个数组a[N], 多次查询 [ l , r ] 的区间和。

当然我们可以用树状数组实现快速查询,但是如果我们增加多次对 [ l, r ] 的区间修改呢?树状数组的单点修改会使复杂度过高,从而TLE。

为了解决对区间的修改与查询的操作,我们引入线段树。

线段树的原理:

简单说,线段树就是通过将长度非一的区间二分递归以维护区间特征值的方法。

如下图,对于给定的数组a[1]--a[5] :

  1. 令d [ i ] 存储某一区间的特征值(这里以区间和为例子),d[1]存储的是 [ 1 , 5 ] 的区间和,d[2]存储的是 [ 1 , 3 ] 的区间和,d[3]存储的是 [ 4 , 5 ] 的区间和。

  2. 在图中的树中d[i]的左右子节点分别为 d [ i * 2 ] 和 d [ i * 2 + 1 ] 。

  3. d[i]存储 [ l , r ] 的特征值,那么左右节点分别存储 [ l , mid ] , [ mid+1 ,r ]的特征值( mid=(l+r)/2 )

![](file://D:\Desk\3700982-20251019202106019-1924872125.png?msec=1762829740594)

代码实现

注意:由于线段树的存储方式,d的长度是a的长度的至少四倍

上传
void push_up(int p)//根据左右子节点更新d[p];
{d[p] = d[ls(p)] + d[rs(p)];
}
建树
void build(int p, int l, int r)//建立[l,r]的线段树,当前访问的节点为p
{t[p] = 0;if (l == r) return d[p] = a[l], void();build(ls(p), l, mid), build(rs(p), mid + 1, r);push_up(p);
}
查询
ll ask(int p, int l, int r, int L, int R)//查询[L,R]的区间和,当前访问节点p,访问区间[l,r];
{if (l > R || r < L)return 0;if (L <= l && r <= R)return d[p];{//push_down(p, l, r);暂时先不管它return ask(ls(p), l, mid, L, R) + ask(rs(p), mid + 1, r, L, R);}
}    
修改

如果我们每一次对区间的修改都进行到叶节点的话,时间复杂度过高。

我们设法当且仅当查询/访问到某个节点时,我们才对它修改。

//利用t[p]来记录t[p]是否作了修改
void add(int p, int l, int r, ll x)//对节点p进行修改:每个元素加x;当前访问[l,r]区间;
{d[p] = (d[p] + x * (r - l + 1));t[p] = t[p] + x;
}
void push_down(int p, int l, int r)//下传,对d[p]的左右子节点进行修改;当前访问区间[l,r];
{if (!t[p]) return ;//t[p]标记为0,则不对d[p]作修改;add(ls(p), l, mid, t[p]), add(rs(p), mid + 1, r, t[p]);//分别修改d[p]的左右子节点;t[p] = 0;//清空t[p]标记;
}void add(int p, int l, int r, int L, int R, ll x)//当前访问节点p,访问区间[l,r];对[L,R]区间中每个元素加x;
{if (l > R || r < L)//访问区间[l,r]与修改区间[L,R]没有交集;return ;if (L <= l && r <= R)//访问区间[l,r]包含于修改区间[L,R];{add(p, l, r, x);return;}else{push_down(p, l, r);//先对左右子节点进行修改add(ls(p), l, mid, L, R, x), add(rs(p), mid + 1, r, L, R, x);push_up(p);}
}

现在我们知道为什么在ask函数中设置push_down了。

例题

均来自Luogu

———————————————————————————————————————————————————————————————————————————————————————————

P3372 【模板】线段树 1

题目描述

如题,已知一个数列 \(\{a_i\}\),你需要进行下面两种操作:

  1. 将某区间每一个数加上 \(k\)
  2. 求出某区间每一个数的和。

输入格式

第一行包含两个整数 \(n, m\),分别表示该数列数字的个数和操作的总个数。

第二行包含 \(n\) 个用空格分隔的整数 \(a_i\),其中第 \(i\) 个数字表示数列第 \(i\) 项的初始值。

接下来 \(m\) 行每行包含 \(3\)\(4\) 个整数,表示一个操作,具体如下:

  1. 1 x y k:将区间 \([x, y]\) 内每个数加上 \(k\)
  2. 2 x y:输出区间 \([x, y]\) 内每个数的和。

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

输入输出样例 #1

输入 #1

5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4

输出 #1

11
8
20

说明/提示

对于 \(15\%\) 的数据:\(n \le 8\)\(m \le 10\)
对于 \(35\%\) 的数据:\(n \le {10}^3\)\(m \le {10}^4\)
对于 \(100\%\) 的数据:\(1 \le n, m \le {10}^5\)\(a_i,k\) 为正数,且任意时刻数列的和不超过 \(2\times 10^{18}\)

【样例解释】

———————————————————————————————————————————————————————————————————————————————————————————
分析

直接由上面的代码即可得出。

代码实现
#include<bits/stdc++.h>
#define ll long long
#define mid ((l+r)>>1)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
const int maxn = 1e5+10;
ll a[maxn], d[maxn << 2], t[maxn << 2];
int n, m;
using namespace std;
void push_up(int p)//上传,根据左右子节点更新d[p];
{d[p] = d[ls(p)] + d[rs(p)];
}
void build(int p, int l, int r)//建立[l,r]的线段树,当前访问的节点为p
{t[p] = 0;if (l == r) return d[p] = a[l], void();build(ls(p), l, mid), build(rs(p), mid + 1, r);push_up(p);
}
//利用t[p]来记录t[p]是否作了修改
void add(int p, int l, int r, ll x)//对节点p进行修改:每个元素加x;当前访问[l,r]区间;
{d[p] = (d[p] + x * (r - l + 1));t[p] = t[p] + x;
}
void push_down(int p, int l, int r)//下传,对d[p]的左右子节点进行修改;当前访问区间[l,r];
{if (!t[p]) return ;//t[p]标记为0,则不对d[p]作修改;add(ls(p), l, mid, t[p]), add(rs(p), mid + 1, r, t[p]);//分别修改d[p]的左右子节点;t[p] = 0;//清空t[p]标记;
}void add(int p, int l, int r, int L, int R, ll x)//当前访问节点p,访问区间[l,r];对[L,R]区间中每个元素加x;
{if (l > R || r < L)//访问区间[l,r]与修改区间[L,R]没有交集;return ;if (L <= l && r <= R)//访问区间[l,r]包含于修改区间[L,R];{add(p, l, r, x);return;}else{push_down(p, l, r);//先对左右子节点进行修改add(ls(p), l, mid, L, R, x), add(rs(p), mid + 1, r, L, R, x);push_up(p);}
}
ll ask(int p, int l, int r, int L, int R)//查询[L,R]的区间和,当前访问节点p,访问区间[l,r];
{if (l > R || r < L)return 0;if (L <= l && r <= R)return d[p];{push_down(p, l, r);暂时先不管它return ask(ls(p), l, mid, L, R) + ask(rs(p), mid + 1, r, L, R);}
}
int main()
{ios::sync_with_stdio(0);cin >> n >> m;for (int i = 1; i <= n; i++)cin >> a[i];build(1, 1, n);while (m--){ll op, x, y, k;cin >> op >> x >> y;if (op == 1){cin >> k;add(1, 1, n, x, y, k);}elsecout << ask(1, 1, n, x, y) << "\n";}return 0;
}

———————————————————————————————————————————————————————————————————————————————————————————

P3373 【模板】线段树 2

题目描述

如题,已知一个数列 \(a\),你需要进行下面三种操作:

  • 将某区间每一个数乘上 \(x\)
  • 将某区间每一个数加上 \(x\)
  • 求出某区间每一个数的和。

输入格式

第一行包含三个整数 \(n,q,m\),分别表示该数列数字的个数、操作的总个数和模数。

第二行包含 \(n\) 个用空格分隔的整数,其中第 \(i\) 个数字表示数列第 \(i\) 项的初始值 \(a_i\)

接下来 \(q\) 行每行包含若干个整数,表示一个操作,具体如下:

操作 \(1\): 格式:1 x y k 含义:将区间 \([x,y]\) 内每个数乘上 \(k\)

操作 \(2\): 格式:2 x y k 含义:将区间 \([x,y]\) 内每个数加上 \(k\)

操作 \(3\): 格式:3 x y 含义:输出区间 \([x,y]\) 内每个数的和对 \(m\) 取模所得的结果。

输出格式

输出包含若干行整数,即为所有操作 \(3\) 的结果。

输入输出样例 #1

输入 #1

5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4

输出 #1

17
2

说明/提示

【数据范围】

对于 \(30\%\) 的数据:\(n \le 8\)\(q \le 10\)
对于 \(70\%\) 的数据:$n \le 10^3 \(,\)q \le 10^4$。
对于 \(100\%\) 的数据:\(1 \le n \le 10^5\)\(1 \le q \le 10^5,1\le a_i,k\le 10^4\)

除样例外,\(m = 571373\)

(数据已经过加强 _

样例说明:

故输出应为 \(17\)\(2\)\(40 \bmod 38 = 2\))。

———————————————————————————————————————————————————————————————————————————————————————————

分析
  1. 本题与上题增加了对区间乘法修改的需求

  2. 考虑如何实现区间乘法:显然 [ l , r ] 中每一个元素乘k,那么区间和乘k;

  3. 由于本题需要实现区间加法的修改,我们还需要考虑下传时加法和乘法的顺序

容易想到,如果修改操作中先乘再加,那么代码中先乘再加

如果修改操作中先加再乘,我们考虑和上面代码中的顺序保持一致:不难发现 (d+x)y=dy+xy

因此我们将乘法标记初始化为1,加法标记初始化为0;

先让加法标记和区间和乘上乘法标记,再让区间和与加法标记相加即可。

代码实现

只需综合分析,对上一题代码稍作修改

#include<bits/stdc++.h>
#define ll long long
#define mid ((l+r)>>1)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
const int maxn = 1e5+10;
ll a[maxn], d[maxn << 2], t[maxn << 2], t1[maxn << 2];
int n, m, pp ;
using namespace std;
void push_up(int p)
{d[p] = (d[ls(p)] + d[rs(p)]) % pp;
}
void build(int p, int l, int r)
{t[p] = 0;t1[p] = 1;if (l == r) return d[p] = a[l], void();build(ls(p), l, mid), build(rs(p), mid + 1, r);push_up(p);
}
void add(int p, int l, int r, ll x)
{d[p] = (d[p] + x * ((r - l + 1) % pp)) % pp;t[p] = (t[p] + x) % pp;
}
void mul(int p, int l, int r, ll x)//区间乘法
{d[p] = (d[p] * (x % pp)) % pp;t[p] = ( t[p] * (x % pp )) % pp;t1[p] = (t1[p] * (x % pp )) % pp;
}
void push_down(int p, int l, int r)
{if ((t1[p] == 1) && !t[p]) return ;mul(ls(p), l, mid, t1[p]), mul(rs(p), mid + 1, r, t1[p]);//下传乘法标记add(ls(p), l, mid, t[p]), add(rs(p), mid + 1, r, t[p]);//下传加法标记t[p] = 0;t1[p] = 1;
}
void add(int p, int l, int r, int L, int R, ll x)
{if (l > R || r < L)return ;if (L <= l && r <= R){add(p, l, r, x);return;}else{push_down(p, l, r);add(ls(p), l, mid, L, R, x), add(rs(p), mid + 1, r, L, R, x);push_up(p);}
}
void mul(int p, int l, int r, int L, int R, ll x)//区间乘法
{if (l > R || r < L)return ;if (L <= l && r <= R){mul(p, l, r, x);return;}else{push_down(p, l, r);mul(ls(p), l, mid, L, R, x), mul(rs(p), mid + 1, r, L, R, x);push_up(p);}
}
ll ask(int p, int l, int r, int L, int R)
{if (l > R || r < L)return 0;if (L <= l && r <= R)return d[p];{push_down(p, l, r);return (ask(ls(p), l, mid, L, R) + ask(rs(p), mid + 1, r, L, R)) % pp;}
}
int main()
{ios::sync_with_stdio(0);cin >> n >> m >> pp;for (int i = 1; i <= n; i++)cin >> a[i];build(1, 1, n);while (m--){ll op, x, y, k;cin >> op >> x >> y;if (op == 1){cin >> k;mul(1, 1, n, x, y, k);}else if (op == 2){cin >> k;add(1, 1, n, x, y, k);}elsecout << ask(1, 1, n, x, y) % pp << "\n";}return 0;
}

———————————————————————————————————————————————————————————————————————————————————————————

P1471 方差

题目背景

滚粗了的 HansBug 在收拾旧数学书,然而他发现了什么奇妙的东西。

题目描述

蒟蒻 HansBug 在一本数学书里面发现了一个神奇的数列,包含 \(N\) 个实数。他想算算这个数列的平均数和方差。

输入格式

第一行包含两个正整数 \(N,M\),分别表示数列中实数的个数和操作的个数。

第二行包含 \(N\) 个实数,其中第 \(i\) 个实数表示数列的第 \(i\) 项。

接下来 \(M\) 行,每行为一条操作,格式为以下三种之一:

操作 \(1\)1 x y k ,表示将第 \(x\) 到第 \(y\) 项每项加上 \(k\)\(k\) 为一实数。
操作 \(2\)2 x y ,表示求出第 \(x\) 到第 \(y\) 项这一子数列的平均数。
操作 \(3\)3 x y ,表示求出第 \(x\) 到第 \(y\) 项这一子数列的方差。

输出格式

输出包含若干行,每行为一个实数,即依次为每一次操作 \(2\) 或操作 \(3\) 所得的结果(所有结果四舍五入保留 \(4\) 位小数)。

输入输出样例 #1

输入 #1

5 5
1 5 4 2 3
2 1 4
3 1 5
1 1 1 1
1 2 2 -1
3 1 5

输出 #1

3.0000
2.0000
0.8000

说明/提示

关于方差:对于一个有 \(n\) 项的数列 \(A\),其方差 \(s^2\) 定义如下:

\[s^2=\frac{1}{n}\sum\limits_{i=1}^n\left(A_i-\overline A\right)^2$$ 其中 $\overline A$ 表示数列 $A$ 的平均数,$A_i$ 表示数列 $A$ 的第 $i$ 项。样例说明: | 操作步骤 | 输入内容 | 操作要求 | 数列 | 输出结果 | 说明 | | :----------: | :----------: | :----------: | :----------: | :----------: | :----------: | | $0$ | - | - | `1 5 4 2 3` | - | - | | $1$ | `2 1 4` | 求 $\left[1,4\right]$ 内所有数字的平均数 | `1 5 4 2 3` | `3.0000` | 平均数 $=\left(1+5+4+2\right)\div 4=3.0000$ | | $2$ | `3 1 5` | 求 $\left[1,5\right]$ 内所有数字的方差 | `1 5 4 2 3` | `2.0000` | 平均数 $=\left(1+5+4+2+3\right)\div 5=3$,方差 $=\left(\left(1-3\right)^2+\left(5-3\right)^2+\left(4-3\right)^2+\left(2-3\right)^2+\left(3-3\right)^2\right)\div 5=2.0000$ | | $3$ | `1 1 1 1` | 将 $\left[1,1\right]$ 内所有数字加 $1$ | `2 5 4 2 3` | - | - | | $4$ | `1 2 2 -1` | 将 $\left[2,2\right]$ 内所有数字加 $-1$ | `2 4 4 2 3` | - | - | | $5$ | `3 1 5` | 求 $\left[1,5\right]$ 内所有数字的方差 | `2 4 4 2 3` | `0.8000` | 平均数 $=\left(2+4+4+2+3\right)\div 5=3$,方差 $=\left(\left(2-3\right)^2+\left(4-3\right)^2+\left(4-3\right)^2+\left(2-3\right)^2+\left(3-3\right)^2\right)\div 5=0.8000$ |数据规模: | 数据点 | $N$ | $M$ | 备注 | | :----------: | :----------: | :----------: | :----------: | | $1\sim3$ | $N\le 8$ | $M\le 15$ | - | | $4\sim7$ | $N\le 10^5$ | $M\le 10^5$ | 不包含操作 $3$| | $8\sim10$ | $N\le 10^5$ | $M\le 10^5$ | - |保证原数列和输入的所有 $k$ 均为 $[-100,100]$ 范围内的实数。———————————————————————————————————————————————————————————————————————————————————————————###### 分析1. 需要维护的区间特征值:平均数和方差2. 平均数:区间和/区间长度,区间和我们已经在前面的例子中说明了3. 方差:直接将方差作为特征值存储到线段树中,对某一区间的修改将变得十分复杂,我们尝试对方差公式进行恒等变形,看看能不能用几个易于维护的特征值表示方差:![c881121b207587054412f2b72253ba81jpg](file:///C:/Users/26695/Documents/Tencent%20Files/1685034571/nt_qq/nt_data/Pic/2025-10/Ori/c881121b207587054412f2b72253ba81.jpg?msec=1762829740594)$\[\frac{\sum_{i = 1}^{n}(x_i - \bar{x})^2}{n} = \frac{\sum_{i = 1}^{n}(x_i^2 - 2x_i\bar{x} + \bar{x}^2)}{n} = \frac{\sum_{i = 1}^{n} x_i^2 - 2\bar{x}\sum_{i = 1}^{n} x_i + n\bar{x}^2}{n} = \frac{\sum_{i = 1}^{n} x_i^2 - n\bar{x}^2}{n}\]$由上图,我们只需要再维护区间平方和就可以求得区间的方差4. 平方和:上传与区间和一致区间修改如图![edf96d9ce8bb53d0d604175058ec16abjpg](file:///C:/Users/26695/Documents/Tencent%20Files/1685034571/nt_qq/nt_data/Pic/2025-10/Ori/edf96d9ce8bb53d0d604175058ec16ab.jpg?msec=1762829740594)借助区间和即可> 注:应先修改平方和,再修改区间和###### 代码实现``` #include<bits/stdc++.h> #define ls(p) (p<<1) #define rs(p) (p<<1|1) #define mid ((l+r)>>1) using namespace std; const int N = 1e5+10; int n, m; double a[N], d[N << 2], d2[N << 2], t[N << 2];//d1存储区间和,d2存储区间平方和,t作为修改标记 void push_up(int p) {d[p] = d[ls(p)] + d[rs(p)];d2[p] = d2[ls(p)] + d2[rs(p)]; } void build(int p, int l, int r) {t[p] = 0;if (l == r){d[p] = a[l];d2[p] = a[l] * a[l];return ;}build(ls(p), l, mid), build(rs(p), mid + 1, r);push_up(p); } void add(int p, int l, int r, double x) {d2[p] += 2 * x * d[p] + x * x * (r - l + 1) ;//修改区间平方和d[p] += x * (r - l + 1);//修改区间和t[p] += x;} void push_down(int p, int l, int r) {if (!t[p])return ;d2[ls(p)] += 2 * t[p] * d[ls(p)] + t[p] * t[p] * (mid - l + 1) ;d2[rs(p)] += 2 * t[p] * d[rs(p)] + t[p] * t[p] * (r - mid) ;d[ls(p)] += t[p] * (mid - l + 1), d[rs(p)] += t[p] * (r - mid);t[ls(p)] += t[p], t[rs(p)] += t[p];t[p] = 0; } void add(int p, int l, int r, double x, int L, int R) {if (l > R || L > r)return ;if (L <= l && r <= R){add(p, l, r, x);return ;}push_down(p, l, r);add(ls(p), l, mid, x, L, R), add(rs(p), mid + 1, r, x, L, R);push_up(p);} double ask1(int p, int l, int r, int L, int R)//查询[l,r]的区间和 {if (l > R || L > r)return 0;if (L <= l && r <= R)return d[p];push_down(p, l, r);return ask1(ls(p), l, mid, L, R) + ask1(rs(p), mid + 1, r, L, R);} double ask2(int p, int l, int r, int L, int R)//查询[l,r]的区间平方和 {if (l > R || L > r)return 0;if (L <= l && r <= R)return d2[p];push_down(p, l, r);return ask2(ls(p), l, mid, L, R) + ask2(rs(p), mid + 1, r, L, R); } int main() {ios::sync_with_stdio(0);cin >> n >> m;for (int i = 1; i <= n; i++)cin >> a[i];build(1, 1, n);while (m--){int op, x, y;double k;cin >> op >> x >> y;if (op == 1){cin >> k;add(1, 1, n, k, x, y);}else if (op == 2){printf("%.4lf\n", ask1(1, 1, n, x, y) / (y - x + 1));}else{double ans = (ask2(1, 1, n, x, y)) / (y - x + 1) -(ask1(1, 1, n, x, y) / (y - x + 1)) * (ask1(1, 1, n, x, y) / (y - x + 1));printf("%.4lf\n", ans);}}return 0; } ```———————————————————————————————————————————————————————————————————————————————————————————# P4513 小白逛公园## 题目背景小新经常陪小白去公园玩,也就是所谓的遛狗啦…## 题目描述在小新家附近有一条“公园路”,路的一边从南到北依次排着 $n$ 个公园,小白早就看花了眼,自己也不清楚该去哪些公园玩了。一开始,小白就根据公园的风景给每个公园打了分。小新为了省事,每次遛狗的时候都会事先规定一个范围,小白只可以选择第 $a$ 个和第 $b$ 个公园之间(包括 $a, b$ 两个公园)选择连续的一些公园玩。小白当然希望选出的公园的分数总和尽量高咯。同时,由于一些公园的景观会有所改变,所以,小白的打分也可能会有一些变化。那么,就请你来帮小白选择公园吧。## 输入格式第一行,两个整数 $n$ 和 $m$,分别表示表示公园的数量和操作(遛狗或者改变打分)总数。接下来 $n$ 行,每行一个整数,依次给出小白开始时对公园的打分。接下来 $m$ 行,每行三个整数。其中第一个整数 $k$ 为 $1$ 或 $2$。- $k=1$ 表示,小新要带小白出去玩,接下来的两个整数 $a$ 和 $b$ 给出了选择公园的范围 $(1 \le a,b \le n)$。测试数据可能会出现 $a > b$ 的情况,需要进行交换; - $k=2$ 表示,小白改变了对某个公园的打分,接下来的两个整数 $p$ 和 $s$,表示小白对第 $p$ 个公园的打分变成了 $s(1\le |s|\le 1000)$。## 输出格式小白每出去玩一次,都对应输出一行,只包含一个整数,表示小白可以选出的公园得分和的最大值。## 输入输出样例 #1### 输入 #1``` 5 3 1 2 -3 4 5 1 2 3 2 2 -1 1 2 3 ```### 输出 #1``` 2 -1 ```## 说明/提示### 数据规模与约定对于 $100\%$ 的数据,$1 \le n \le 5 \times 10^5$,$1 \le m \le 10^5$,所有打分都是绝对值不超过 $1000$ 的整数。———————————————————————————————————————————————————————————————————————————————————————————###### 分析1. 查询连续子段和的最大值(不得为空):d[p]对应区间的最大子段和只有如图的几种情况![](file://D:\Desk\50d3f40c1837b4c20baafaf7f5476fad.jpg?msec=1762829740596)2. 由1.我们知道:至少要维护区间的最大前缀和,最大后缀和,最大连续子段和这几个特征值3. 考虑最大前/后缀和的维护:d[p]对应区间的最大前/后缀和只有如图的两种情况![](file://D:\Desk\690E9D69793EB50892C1A5F02C5B523C.jpg?msec=1762829740597)###### 代码实现> 注:查询函数ask与上文有些许不同 > > ![ed687d91db003bd5925c2d3972509058jpg](file:///C:/Users/26695/Documents/Tencent%20Files/1685034571/nt_qq/nt_data/Pic/2025-10/Ori/ed687d91db003bd5925c2d3972509058.jpg?msec=1762829740596) > > 查询区间(阴影)与访问区间(d[p])无非如图的三种关系(在阴影包含于d[p]时),如果照搬上面三题中ask函数的判断逻辑(查询区间超出访问区间的部分将不再计算)那么第三种情况中求出的是查询区间分割成两部分后的两个区间最大连续子段和,不能转化为原查询区间的连续子段和,所以我们对ask函数的判断逻辑作出修改(详见代码中ask)``` #include <bits/stdc++.h> using namespace std; #define ll long long #define ls(p) (p << 1) #define rs(p) (p << 1 | 1) #define mid ((l + r) >> 1) const int N = 1e5 + 10; const ll INF = 1e18;ll a[N]; //用结构体Node存储区间特征值 struct Node {ll maxl, maxx, maxr, sum; } d[N << 2];int n, m; //合并left的特征值 和 right的特征值 , 得到新的特征值 Node merge(Node left, Node right) {Node res;res.sum = left.sum + right.sum;res.maxl = max(left.maxl, left.sum + right.maxl);res.maxr = max(right.maxr, right.sum + left.maxr);res.maxx = max({left.maxx, right.maxx, left.maxr + right.maxl});return res; }void push_up(int p) {d[p] = merge(d[ls(p)], d[rs(p)]); }void build(int p, int l, int r) {if (l == r){d[p].sum = a[l];d[p].maxl = a[l];d[p].maxr = a[l];d[p].maxx = a[l];return;}build(ls(p), l, mid);build(rs(p), mid + 1, r);push_up(p); }void update(int p, int l, int r, int pos, ll val)//将a[pos]更改为val {if (l == r){a[l] = val;d[p].sum = val;d[p].maxl = val;d[p].maxr = val;d[p].maxx = val;return;}if (pos <= mid)//pos在左节点对应区间{update(ls(p), l, mid, pos, val);}else//pos在右节点对应区间{update(rs(p), mid + 1, r, pos, val);}push_up(p); }Node ask(int p, int l, int r, int L, int R) {// 完全包含if (L <= l && r <= R){return d[p];}// 只在左子树if (R <= mid){return ask(ls(p), l, mid, L, R);}// 只在右子树if (L >= mid+1){return ask(rs(p), mid + 1, r, L, R);}// 跨越中点:分别查询[L, mid]和[mid+1, R]Node left = ask(ls(p), l, mid, L, mid);Node right = ask(rs(p), mid + 1, r, mid + 1, R);return merge(left, right); }int main() {ios::sync_with_stdio(0);cin.tie(0);cin >> n >> m;for (int i = 1; i <= n; i++){cin >> a[i];}build(1, 1, n);while (m--){int k, x, y;cin >> k >> x >> y;if (k == 1){if (x > y) swap(x, y);Node res = ask(1, 1, n, x, y);cout << res.maxx << "\n";}else{update(1, 1, n, x, y);}}return 0; } ```———————————————————————————————————————————————————————————————————————————————————————————# P7492 [传智杯 #3 决赛] 序列## 题目背景disangan233 正在数数,他希望你帮他记录数数的序列,并完成一些操作。## 题目描述你有一个长为 $n$ 的序列 $a$,现在要对其进行 $m$ 次操作。操作分为两种:1. 给定两个整数 $l,r$,表示询问 $l$ 到 $r$ 的最大连续子段和。 2. 给定三个整数 $l,r,k$,表示将 $l$ 到 $r$ 的 $a_i$ 都按位或上一个 $k$。对于所有数据,$n,m\leq 10^5$,$-2^{30}\leq a_i,k<2^{30}$,$1\leq l\leq r\leq n$。注意:负数按照 32 位补码取按位或。## 输入格式输入共 $m+2$ 行。第 $1$ 行输入 $2$ 个正整数 $n,m$。第 $2$ 行输入 $n$ 个整数 $a_1\ldots a_n$。接下来 $m$ 行,每行先输入 $1$ 个正整数 $op$,$op=1$ 输入 $2$ 个整数 $l,r$ 表示一次询问,否则输入 $3$ 个整数 $l,r,k$ 表示一次修改。## 输出格式输出共若干行,每行共 $1$ 个整数,表示询问的答案。## 输入输出样例 #1### 输入 #1``` 15 15 512 -65 33554432 32 8194 13 16 2 67108872 131072 -8192 8194 16 2048 4096 1 3 5 1 10 10 2 1 7 671367424 1 8 14 1 5 11 2 13 13 335579137 2 2 13 5376 1 2 5 2 5 6 8392768 1 1 2 2 2 14 201335872 2 1 14 0 1 11 12 1 8 12 1 4 9 ```### 输出 #1``` 33562658 131072 67242012 2081350441 2047680290 671367936 201340226 805489228 3373416393 ```———————————————————————————————————————————————————————————————————————————————————————————###### 分析   1. 需要对区间修改,并且需要区间最大连续子段和查询> 注:这里子段可以为空,此时子段和为0; > > 笔者因为没考虑到这一点调试代码浪费好长时间2. 区间最大非空子段和的查询同上一道题,若最大非空子段和为负数,那么输出0即可满足本题要求下面考虑如何快速对区间进行按位或的操作:3. 如果k=0,相当于没有修改4. 如果a[x]|k=a[x],相当于没有修改5. 按位或操作最多执行到每一位都是1,操作次数有上限,综合3,4我们可以跳过许多不必要的线段树的遍历/修改6. 放到区间里面来看,如果某区间的所有元素的在k中为1的位数也都为1的话,那么不必进行修改7. 第6.的判断我们可以借助按位与来完成:区间特征值一位为1,当且仅当区间中所有元素的该位是1```struct Node{ll max1, max2, maxn, sum;ll and_val; // 区间按位与} d[N << 2];void push_up(int p){...d[p].and_val = d[ls(p)].and_val & d[rs(p)].and_val; // 维护按位与}```###### 代码实现> 这里ask函数如同上一题``` #include<bits/stdc++.h> using namespace std; #define ll long long #define ls(p) (p<<1) #define rs(p) (p<<1|1) #define mid ((l+r)>>1) const int N = 1e5+10; const ll INF = -1e15+10;struct Node {ll max1, max2, maxn, sum;ll and_val; // 区间按位与 } d[N << 2];ll a[N]; int n, m;void push_up(int p) {d[p].sum = d[ls(p)].sum + d[rs(p)].sum;d[p].max1 = max(d[ls(p)].max1, d[ls(p)].sum + d[rs(p)].max1);d[p].max2 = max(d[rs(p)].max2, d[ls(p)].max2 + d[rs(p)].sum);d[p].maxn = max({d[ls(p)].maxn, d[rs(p)].maxn, d[ls(p)].max2 + d[rs(p)].max1});d[p].and_val = d[ls(p)].and_val & d[rs(p)].and_val; // 维护按位与 }void build(int p, int l, int r) {if (l == r){d[p] = {a[l], a[l], a[l], a[l], a[l]};return;}build(ls(p), l, mid);build(rs(p), mid + 1, r);push_up(p); }void update(int p, int l, int r, int L, int R, ll k) {if (r < L || l > R) return;if (l == r) // 叶子节点直接更新{if ((a[l] | k) == a[l]) return;a[l] |= k;d[p] = {a[l], a[l], a[l], a[l], a[l]};return;}if (L <= l && r <= R){if (k == 0) return;if ((k & ~d[p].and_val) == 0) return; // 剪枝:k的1位在区间内已全为1}if (R <= mid) update(ls(p), l, mid, L, R, k);else if (L > mid) update(rs(p), mid + 1, r, L, R, k);else{update(ls(p), l, mid, L, R, k);update(rs(p), mid + 1, r, L, R, k);}push_up(p); }Node ask(int p, int l, int r, int L, int R) {if (L <= l && r <= R) return d[p];if (R <= mid) return ask(ls(p), l, mid, L, R);if (L > mid) return ask(rs(p), mid + 1, r, L, R);Node left = ask(ls(p), l, mid, L, mid);Node right = ask(rs(p), mid + 1, r, mid + 1, R);Node res;res.sum = left.sum + right.sum;res.max1 = max(left.max1, left.sum + right.max1);res.max2 = max(right.max2, left.max2 + right.sum);res.maxn = max({left.maxn, right.maxn, left.max2 + right.max1});return res; }int main() {ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n >> m;d[0] = {INF, INF, INF, 0, 0}; // 初始化空节点for (int i = 1; i <= n; ++i) cin >> a[i];build(1, 1, n);while (m--){ll op, l, r, k;cin >> op;if (op == 1){cin >> l >> r;if (ask(1, 1, n, l, r).maxn >= 0)cout << ask(1, 1, n, l, r).maxn << '\n';elsecout << 0 << "\n";}else{cin >> l >> r >> k;update(1, 1, n, l, r, k); // 区间更新}}return 0; } ```———————————————————————————————————————————————————————————————————————————————————————————## ~~硬是要总结~~线段树十分利好多次查询区间特征值的问题,尤其当特征值满足结合律,可差分时。当遇到区间特征值的修改与查询时,不妨考虑线段树。## 参考文献1. OI-WIKI [OI WIKI 线段树](https://oi-wiki.org/ds/seg/)2. 《统计的力量》——张昆玮[github.com](https://github.com/tiankonguse/lab/blob/master/acm/paper/%E7%BB%9F%E8%AE%A1%E7%9A%84%E5%8A%9B%E9%87%8F%E2%80%94%E2%80%94%E7%BA%BF%E6%AE%B5%E6%A0%91%E8%AF%A6%E7%BB%86%E6%95%99%E7%A8%8B.pdf)3. 《深入浅出程序设计(进阶篇)》——洛谷4. 个人博客[树状数组和线段树基础 - Ahui2667d - 博客园](https://www.cnblogs.com/Ahui2667d/p/19151008)\]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/962164.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络安全审计技术原理与应用 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

在CentOS 7系统中彻底移除MongoDB数据库的步骤

彻底移除CentOS 7系统中的MongoDB数据库,需要进行以下步骤:停止MongoDB服务:首先确保MongoDB服务已经停止,可以通过下面的命令来执行这一操作:sudo systemctl stop mongod 如果您的MongoDB服务名称不是默认的 mon…

为什么越来越多软件开发极速版?

在当今移动互联网时代,越来越多的软件开发者不约而同地推出了“极速版”应用。这种趋势不仅席卷了消费级应用,也逐步渗透到企业级软件领域。极速版应用的兴起并非偶然,而是开发者对市场变化、用户需求和商业策略的综…

2025年河北数据标注公司权威推荐榜单:数字内容制作/数据服务/数据服务源头公司精选

在人工智能技术加速渗透各行业的当下,数据标注作为AI模型训练的关键环节,其行业发展态势备受关注。数据显示,2025年中国数据标注行业迎来爆发式增长,市场规模预计突破百亿元,政策红利叠加自动驾驶、AI大模型等场景…

2025年阜阳民事纠纷律师十大权威排名:专业选择指南与口碑解析

摘要 随着我国法治建设的不断深入,民事纠纷律师行业在阜阳地区呈现快速发展态势。2025年,阜阳民事纠纷律师服务需求同比增长23.6%,行业专业化程度显著提升。本文基于权威数据分析和用户真实评价,为您呈现阜阳地区民…

2025年阜阳民事纠纷律师口碑推荐榜

摘要 随着法律意识的提升,阜阳地区民事纠纷案件逐年增加,对专业律师的需求日益旺盛。2025年,民事纠纷律师行业呈现出专业化、数字化趋势,AI技术辅助但人性化服务仍为核心。本文基于行业数据和用户口碑,整理出口碑…

完整教程:网上商城|基于SprinBoot+vue的分布式架构网上商城系统(源码+数据库+文档)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025年环氧地坪施工方案优质厂家权威推荐榜单:环氧地坪施工/环氧地坪工程/环氧耐磨地坪源头厂家精选

在当今工业与商业建筑标准日益提升的背景下,环氧地坪以其耐磨、防尘、美观等优异特性,成为各类场所地面建设的首选方案。市场需求调查显示,环氧地坪在工业厂房中的应用占比高达65%以上,在商业空间的使用率也呈现年…

逆向基础--C++ 存储类 (04)

逆向基础--C++ 存储类 (04)一.存储类介绍C++ 存储类用于定义变量/函数的生命周期和可见性。它们说明了变量在程序中的存储位置以及如何被访问。下面列出 C++ 程序中可用的存储类:1) auto (自动存储类)  在C++11之前…

题解:P10136 [USACO24JAN] Cowlendar S

鸽巢原理,比较思维早上打模拟赛败在你上面,下午改题改了半天,晚上又在 Ad-hoc 题单里相见,那我就写篇题解纪念。 题目链接 思路 一般这种取模题三个套路:取模余数相同 \(\rightarrow\) 作差之后值为模数的倍数; …

WinForm中C#扫描枪功能实现(含USB/串口方案)

一、技术选型与设备连接 1. 扫描枪连接方式对比类型 通信协议 适用场景 实现复杂度USB模拟键盘 HID协议 快速部署,无需驱动 ★☆☆☆☆串口通信 RS-232/USB转串 工业级设备,定制化需求 ★★★☆☆网络通信 TCP/IP 远…

类方法和静态方法

@classme Python会自动绑定类方法的第一个参数,类方法的第一个参数(通常定义为cls)会自动绑定到类本身 但对于静态方法则不会自动绑定

实用指南:新手上手:基于Rokid CXR-M和CXR-S SDK构建简易翻译助手

实用指南:新手上手:基于Rokid CXR-M和CXR-S SDK构建简易翻译助手pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "…

2025年新疆租车公司权威推荐榜单:新疆自驾游租车/新疆租皮卡车/新疆租通勤班车服务公司精选

广袤天地间,一辆靠谱的车是你探索新疆的最佳旅伴。 随着新疆旅游市场持续升温,2024年全区接待游客突破2.5亿人次,租车自驾作为灵活自由的出行方式,市场需求显著增长。 据行业数据显示,2025年新疆租车市场规模预计…

基于C#和NModbus4库实现的Modbus RTU串口通信

基于C#和NModbus4库实现的Modbus RTU串口通信,包含完整的界面设计和功能实现:一、项目依赖配置NuGet包安装: Install-Package NModbus4 Install-Package System.IO.Ports窗体控件布局: <!-- 基础控件配置 -->…

学习如何创建 Mono 实例

学习如何创建 Mono 实例 Flux 描述 AMono<T>是一个响应式流 Publisher,还增强了许多可用于生成、转换、编排 Mono 序列的操作符。 它是最多 Flux 可以发出 1 个<T>元素的特例:Mono 要么有值(包含元素)…

go_micro框架的安装和使用 - 详解

go_micro框架的安装和使用 - 详解2025-11-11 10:40 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !impor…

2025年超融合产品推荐排行榜

2025年超融合产品推荐排行榜拓实超融合产品 - 综合评分第一产品亮点 一体化架构:计算、存储、网络深度融合 智能运维:自动化部署与智能监控 高可用性:99.99%业务连续性保障 弹性扩展:按需扩容,灵活配置 技术优势 …

2025年北京cppm面授培训机构权威推荐榜单:cppm课程培训/cppm认证报名/cppm认证培训源头机构精选

在当今全球化的商业环境中,采购与供应链管理已成为企业核心竞争力的重要组成部分。CPPM(注册职业采购经理)认证作为采购与供应链领域的权威职业资格,其市场需求量年均增长率稳定在15%以上,已成为众多企业选拔和评…

N-gram

📝 N-gram 1. N-gram 的定义 N-gram 是指一段文本中连续出现的 N 个词或字符的序列。它是一种特征工程(Feature Engineering)技术,将文本数据转化为机器学习模型可以理解的数字特征。N 是一个变量,代表序列的长…