线段树入门详解
本文适用人群
不算特别入门啦~~.
本文适合大概知道线段树原理的,但是对于线段树操作不是很熟悉的oier or Acmer.
本人水平有限,没有过多深究数据结构.大神勿喷.如果有疑问,请立刻在评论区提出,谢谢.
如有错误,敬请指出.
线段树大致思路
看到数据范围为\(nlog n\)的序列.
大致是用线段树做.
做线段树题目的套路
1.将题目化简成数学式子,化成可以维护的东西.
2.考虑维护那些信息.
3.如何合并
4.如何下放
5.如何查询
本人代码习惯
为方便理解代码,特此说明
struct Seg {
int l,r,sum;
int lazy;
}tree[];
l维护当前所管辖区间的左端点
r维护当前所管辖区间的右端点
sum 维护区间和
lazy 是懒惰标记.(延时下放的信息)
还有其他维护的信息会说明.
tree是线段树啦.
struct 包装起来会慢一些,但是较容易理解且不容易出错.
我的线段树要开4倍空间.
#define lson now << 1
#define rson now << 1 | 1
void build(int l,int r,int now) {
tree[now].l = l;tree[now].r = r;
if(l == r) {
XXX;
return;
}
int mid = (l + r) >> 1;
build(l,r,lson);
build(l,r,rson);
updata(now);
}
以上为建树过程.
now是当前节点
lson 是左孩子
rson 是右孩子
updata是上传给父亲结点的信息的函数.
如区间和这个的updata函数就是
void updata(int now) {
tree[now].sum = tree[lson].sum + tree[rson].sum;
return;
}
如有其它函数,下文会进行说明.
单点修改
单点修改.
void change(int pos,int val,int now) {
if(tree[now].l == tree[now].r) {
tree[now].sum += val;
return ;
}
int mid = (tree[now].l + tree[now].r) >> 1;
if(pos <= mid) change(pos,val,lson);
else change(pos,val,rson);
updata(now);
return;
}
这个函数理解起来非常的容易.
如果查询的位置在左儿子的管辖范围就去左儿子修改.
右儿子同理.
updata函数不要忘记,上面的信息也是要修改的.
修改函数需要updata,不要忘记.
下面是四个线段树简单函数的举例.
单点查询
同上面的单点修改.
注意下代码,没有什么好说的.
大概是线段树最简单的函数了...
int find(int pos,int now) {
if(tree[now].l == tree[now].r) {
return tree[now].sum;
}
int mid = (tree[now].l + tree[now].r) >> 1;
if(pos <= mid) return find(pos,lson);
else return find(pos,rson);
}
区间查询
因为每一个线段树上的点都是维护区间完整的信息.
这个点维护区间信息的区间要完全在查询区间范围内才可以对答案进行贡献,否则继续分下去.
这里我们以查询区间的和为例.
int query(int l,int r,int now) {
if(tree[now].l >= l && tree[now].r <= r)
return tree[now].sum;
int mid = (tree[now].l + tree[now].r) >> 1,sum = 0;
if(mid >= l ) sum += query(l,r,lson);
if(mid < r) sum += query(l,r,rson);
return sum;
}
这一部分函数开始较难理解.
这里重点讲解一下如何判断区间求交.
这里如果查询到now节点,还需要继续分下去查询.说明now管辖的区间一定与查询的区间有交集.
我们这里考虑的是.
左儿子和需要查询的区间有没有交集
右儿子和需要查询的区间有没有交集
所以就有了下面的if判断.
注意合起来的信息向上传递.
区间加减
这个时候就要用到懒惰标记了.
懒惰标记是一个非常神奇的东西.
懒惰标记讲究的就是延时下放.
当区间加减时暴力更新还不如\(n^2\)算法.
懒惰标记就是解决区间进行操作的.
懒惰标记的使用:无论是查询,操作.都要下放标记.
为什么?
一是比较方便,容易操作,多余的下放无所谓
二是有些有关操作顺序的
比如区间赋值成一值.
如何下放标记?
视题目而定.
有些题目要打多个标记,记得想一下标记的优先级.
这样就多了三个函数.pushdown 与 modify 和 work
这里的lazy标记是需要加的值.
三个函数的介绍.
work函数
void work(int now , int val) {
tree[now].sum += (tree[now].r - tree[now].l + 1) * val;
tree[now].lazy += val;
}
这个函数是对now线段树节点所管辖的区间加上一个数.
pushdown函数
void pushdown(int now) {
work(lson,tree[now].lazy);
work(rson,tree[now].lazy);
tree[now].lazy = 0;
return;
}
pushodown函数
没有什么好讲的....
注意lazy标记的初始化(这里是归零)
modify函数
void modify(int l,int r,int now,int val) {
if(tree[now].l >= l && tree[now].r <= r) {
work(now,val);
return ;
}
int mid = (tree[now].l + tree[now].r) >> 1;
if(tree[now].lazy) pushdown(now);
if(mid >= l ) modify(l,r,lson,val);
if(mid < r) modify(l,r,rson,val);
updata(now);
return ;
}
然而就这些了...
Last
线段树作为最常用的数据机构之一.非常多的套路,以后会一一列出.
下面给出几道例题模板题
尝试自己码出来
1.模板 树状数组1
2.模板 树状数组2
3.模板 线段树1
4.模板 线段树2
5.简单线段树模板题
1.模板题
#include <iostream>
#include <cstdio>
const int maxN = 1e6 + 7;
int a[maxN];
inline int max(int x,int y) {
return x > y ? x : y;
}
struct Node{
int l,r,maxx;
int w;
}tree[maxN << 2];
void pushup(int now) {
tree[now].w = tree[now << 1].w + tree[now << 1 | 1].w;
tree[now].maxx = max(tree[now << 1].maxx,tree[now << 1 | 1].maxx);
}
void build(int now,int l,int r) {
tree[now].l = l;tree[now].r = r;
if(l == r) {tree[now].w = a[l];return;}
int mid = (l + r) >> 1;
build(now << 1,l,mid);
build(now << 1 | 1,mid + 1,r);
pushup(now);
}
int query(int l,int r,int now) {
if(tree[now].l >= l && tree[now].r <= r) return tree[now].w;
int Ans = 0;
int mid = (tree[now].r + tree[now].l) >> 1;
if(mid >= l) {Ans += query(l,r,now << 1);}
if(mid < r) {Ans += query(l,r,now << 1 | 1);}
return Ans;
}
inline int read() {
int x = 0,f = 1;char c = getchar();
while(c < '0' || c > '9') {if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
void chang(int x,int now,int pos) {
tree[now].w += x;
if(tree[now].l == tree[now].r) return;
int mid = (tree[now].r + tree[now].l) >> 1;
if(mid >= pos) chang(x,now << 1,pos);
else chang(x,now << 1 | 1,pos);
return;
}
int main() {
int n,m;
n = read();m = read();
for(int i = 1;i <= n;++ i)
a[i] = read();
build(1,1,n);
for(int i = 1,emm,l,r;i <= m;++ i) {
emm = read();
l = read();r = read();
if(emm == 1)chang(r,1,l);
else printf("%d\n",query(l,r,1));
}
return 0;
}
2.模板题
#include <iostream>
#include <cstdio>
#define X 500007
struct Node{
int l,r,w,lazy;
}tree[X << 2];
void update(int now){
tree[now].w = tree[now << 1].w + tree[now << 1 | 1].w;
return;
}
void down(int now){
int lazy = tree[now].lazy;
if(lazy){
tree[now << 1].lazy += lazy;
tree[now << 1 | 1].lazy += lazy;
tree[now << 1].w += lazy * (tree[now << 1].r - tree[now << 1].l + 1);
tree[now << 1 | 1].w += lazy * (tree[now << 1 | 1].r - tree[now << 1 | 1].l + 1);
tree[now].lazy = 0;
}
}
void build(int l,int r,int now){
tree[now].l = l;tree[now].r = r;
if(l == r){
scanf("%d",&tree[now].w);
return;
}
int mid = (l + r) >> 1;
build(l,mid,now << 1);
build(mid + 1,r,now << 1 | 1);
update(now);
}
void add_inter(int l,int r,int now,int val){
if(tree[now].l >= l && tree[now].r <= r){
tree[now].w += val * (tree[now].r - tree[now].l + 1);
tree[now].lazy += val;
return;
}
int mid = (tree[now].l + tree[now].r ) >> 1;
if(l <= mid){add_inter(l,r,now << 1,val);}
if(r > mid){add_inter(l,r,now << 1 | 1,val);}
update(now);
return;
}
int Point_ask(int pos,int now){
if(tree[now].l == tree[now].r){
return tree[now].w;
}
down(now);
int mid = (tree[now].r + tree[now].l) >> 1;
if(pos <= mid)return Point_ask(pos,now << 1);
else return Point_ask(pos,now << 1 | 1);
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
build(1,n,1);
for(int i = 1,x;i <= m;++ i){
scanf("%d",&x);
if(x == 1){
int l,r,val;
scanf("%d%d%d",&l,&r,&val);
add_inter(l,r,1,val);
}else{
int xx;
scanf("%d",&xx);
printf("%d\n",Point_ask(xx,1));
}
}
}
3.模板题
#include <iostream>
#include <cstdio>
const int maxN = 1e5 + 7;
#define ll long long
inline int read() {
int x = 0,f = 1;char c = getchar();
while(c < '0' || c > '9') {if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
struct Node{
ll l,r,w,lazy;
}tree[maxN << 2];
ll a[maxN];
void pushdown(ll now) {
ll lazy = tree[now].lazy;
tree[now].lazy = 0;
tree[now << 1].lazy += lazy;
tree[now << 1 | 1].lazy += lazy;
tree[now << 1].w += (tree[now << 1].r - tree[now << 1].l + 1) * lazy;
tree[now << 1 | 1].w += (tree[now << 1 | 1].r - tree[now << 1 | 1].l + 1) * lazy;
return;
}
void updata(ll now) {
tree[now].w = tree[now << 1].w + tree[now << 1 | 1].w;
}
void build(ll l,ll r,ll now) {
tree[now].l = l;tree[now].r = r;
if(l == r) {
tree[now].w = a[l];
return;
}
ll mid = (l + r ) >> 1;
build(l,mid,now << 1);
build(mid + 1,r,now << 1 | 1);
updata(now);
}
void Inter_add(ll l,ll r,ll now,ll val) {
if(tree[now].l >= l && tree[now].r <= r) {
tree[now].w += (tree[now].r - tree[now].l + 1) * val;
tree[now].lazy += val;
return;
}
pushdown(now);
ll mid = (tree[now].l + tree[now].r) >> 1;
if(l <= mid) Inter_add(l,r,now << 1,val);
if(r > mid) Inter_add(l,r,now << 1 | 1,val);
updata(now);
return;
}
ll query(ll l,ll r,ll now) {
if(tree[now].l >= l && tree[now].r <= r) {
return tree[now].w;
}
if(tree[now].lazy)pushdown(now);
ll Ans = 0;
ll mid = (tree[now].l + tree[now].r) >> 1;
if(l <= mid) Ans += query(l,r,now << 1);
if(r > mid ) Ans += query(l,r,now << 1 | 1);
return Ans;
}
int main() {
int n = read(),m = read();
for(int i = 1;i <= n;++ i)
a[i] = read();
build(1,n,1);
for(int i = 1,opt,l,r;i <= m;++ i) {
opt = read();l = read();r = read();
if(opt == 1) {
ll val;
val = read();
Inter_add(l,r,1,val);
}
if(opt == 2) {
printf("%lld\n",query(l,r,1));
}
}
return 0;
}
- 注意加法标记和乘法标记的优先级这里采用乘法标记优先的顺序进行更新.
#include <iostream>
#include <cstdio>
#define ll long long
const ll maxN = 100000 + 7;
using namespace std;
ll a[maxN];
ll mod;
struct Node{
ll l,r,sum;
ll mul,add;
Node() {
add = sum = 0;mul = 1;
}
}tree[maxN << 2];
void qwq(ll now,ll mul,ll add) {
tree[now].mul = tree[now].mul * mul % mod;
tree[now].add = ( tree[now].add * mul % mod + add) % mod;
tree[now].sum = ( (tree[now].sum * mul) % mod +( (tree[now].r - tree[now].l + 1) * add) % mod ) % mod;
}
void updata(ll now) {
tree[now].sum = tree[now << 1].sum + tree[now << 1 | 1].sum;
return;
}
void pushdown(ll now) {
qwq(now << 1,tree[now].mul,tree[now].add);
qwq(now << 1 | 1,tree[now].mul,tree[now].add);
tree[now].mul = 1;
tree[now].add = 0;
}
void build(ll l,ll r,ll now) {
tree[now].l = l;tree[now].r = r;
if(l == r) {
tree[now].sum = a[l];
return;
}
ll mid = (l + r) >> 1;
build(l,mid,now << 1);
build(mid + 1,r,now << 1 | 1);
updata(now);
}
ll query(ll L,ll R,ll now) {
ll l = tree[now].l ,r = tree[now].r;
if(l >= L && r <= R) return tree[now].sum % mod;
if(tree[now].add || tree[now].mul != 1) pushdown(now);
ll sum = 0;
if(L <= (r + l ) >> 1) sum += query(L,R,now << 1);sum %= mod;
if(R > (r + l ) >> 1) sum += query(L,R,now << 1 | 1);
return sum % mod;
}
void Inter_change(ll L,ll R,ll now,ll add,ll mul) {
ll l = tree[now].l,r = tree[now].r;
if(l >= L && r <= R) {
qwq(now,mul,add);
return;
}
ll mid = (r + l) >> 1;
if(tree[now].add || tree[now].mul != 1) pushdown(now);
if(L <= mid) Inter_change(L,R,now << 1,add,mul);
if(R > mid) Inter_change(L,R,now << 1 | 1,add,mul);
updata(now);
}
int main()
{
ll n,m;
scanf("%lld%lld%lld",&n,&m,&mod);
for(ll i = 1;i <= n;++ i)
scanf("%lld",&a[i]);
build(1,n,1);
ll opt,l,r,k;
while(m --) {
scanf("%lld%lld%lld",&opt,&l,&r);
if(opt != 3) {
scanf("%lld",&k);
if(opt == 1) {
Inter_change(l,r,1,0,k);
}
if(opt == 2) {
Inter_change(l,r,1,k,1);
}
}
else printf("%lld\n", query(l,r,1));
}
return 0;
}
- 化简式子
注意一下乘一个数,加一个数时,如何更新.即可.
注意更新的优先级.
#include <iostream>
#include <cstdio>
#define lson now << 1
#define rson now << 1 | 1
#define ll long long
const ll maxN = 10000 + 7;
struct Node {
ll l,r;
ll sum[3];
ll lazy,tag;
}tree[maxN << 2];
void updata(ll now) {
tree[now].sum[1] = tree[lson].sum[1] + tree[rson].sum[1];
tree[now].sum[2] = tree[lson].sum[2] + tree[rson].sum[2];
return ;
}
void build(ll l,ll r,ll now) {
tree[now].l = l;tree[now].r = r;
tree[now].tag = 1;
if(l == r) {
scanf("%d",&tree[now].sum[1]);
tree[now].sum[2] = tree[now].sum[1] * tree[now].sum[1];
return;
}
ll mid = (l + r) >> 1;
build(l,mid,lson);
build(mid + 1,r,rson);
updata(now);
return;
}
void work(ll now,ll lazy,ll tag) {
tree[now].sum[2] = tree[now].sum[2] * tag;
tree[now].sum[1] = tree[now].sum[1] * tag;
tree[now].sum[2] += 2 * tree[now].sum[1] * lazy + (tree[now].r - tree[now].l + 1) * lazy * lazy;
tree[now].sum[1] += (tree[now].r - tree[now]. l + 1) * lazy;
tree[now].lazy = tree[now].lazy * tag + lazy;
tree[now].tag *= tag;
return;
}
void pushdown(ll now) {
work(lson,tree[now].lazy,tree[now].tag);
work(rson,tree[now].lazy,tree[now].tag);
tree[now].lazy = 0;
tree[now].tag = 1;
return ;
}
ll query_1(ll l,ll r,ll now) {
if(tree[now].l >= l && tree[now].r <= r)
return tree[now].sum[1];
ll mid = (tree[now].l + tree[now].r) >> 1,sum = 0;
if(tree[now].lazy || tree[now].tag != 1) pushdown(now);
if(mid >= l) sum += query_1(l,r,lson);
if(mid < r) sum += query_1(l,r,rson);
return sum;
}
ll query_2(ll l,ll r,ll now) {
if(tree[now].l >= l && tree[now].r <= r)
return tree[now].sum[2];
ll mid = (tree[now].l + tree[now].r) >> 1,sum = 0;
if(tree[now].lazy || tree[now].tag != 1) pushdown(now);
if(mid >= l) sum += query_2(l,r,lson);
if(mid < r) sum += query_2(l,r,rson);
return sum;
}
void modify(ll l,ll r,ll now,ll lazy,ll tag) {
if(tree[now].l >= l && tree[now].r <= r) {
work(now,lazy,tag);
return ;
}
ll mid = (tree[now].l + tree[now].r) >> 1;
if(tree[now].lazy || tree[now].tag != 1) pushdown(now);
if(mid >= l) modify(l,r,lson,lazy,tag);
if(mid < r) modify(l,r,rson,lazy,tag);
updata(now);
return ;
}
int main() {
ll n,m;
scanf("%lld%lld",&n,&m);
build(1,n,1);
ll opt,l,r,x;
while(m --) {
scanf("%lld%lld%lld",&opt,&l,&r);
if(opt == 1) printf("%lld\n",query_1(l,r,1));
if(opt == 2) printf("%lld\n", query_2(l,r,1));
if(opt == 3) {
scanf("%lld",&x);
modify(l,r,1,0,x);
}
if(opt == 4) {
scanf("%lld",&x);
modify(l,r,1,x,1);
}
}
return 0;
}
这里还有一道码农题要不要做一下.
哪的题目忘记了.
题目
0 l r 询问区间[l,r]内的元素和
1 l r 询问区间[l,r]内的元素的立方 和
2 l r 询问区间[l,r]内的元素的平方 和
3 l r x 将区间[l,r]内的每一个元素都乘上x
4 l r x 将区间[l,r]内的每一个元素都加上x
5.l r x 将区间[l,r]内的每一个元素赋值为x
有时间把它挂到个人题库里面,有时间会更新线段树进阶的.