-
前言
本章不是线段树教程,包含内容是博主自己刷题时的整理,请不要转载
训练地址:一本通线段树地址 (剩余在每个板块都有链接
-
单点修改,区间查询
没什么好说的,注意每次修改要记得pushup
附板子,0为单点修改,1为查询区间和
一般这样还是搞树状数组吧 这破玩意常数太大卡死了
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
#include<iomanip>
using namespace std;
#define int long long
inline int read()
{
int x=0,y=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
const int maxn=100005;
int n,m;
int tree[maxn*4];
void push_up(int id)
{
tree[id]=tree[id<<1]+tree[id<<1|1];
return ;
}
void modify(int id,int l,int r,int pos,int x)
{
if(l==r) {tree[id]+=x; return ;}
int mid=(l+r)>>1;
if(pos<=mid) modify(id<<1,l,mid,pos,x);
else modify(id<<1|1,mid+1,r,pos,x);
push_up(id);
return ;
}
int query(int id,int l,int r,int x,int y)
{
if(l>=x&&r<=y) return tree[id];
int ans=0;
int mid=(l+r)>>1;
if(x<=mid) ans+=query(id<<1,l,mid,x,y);
if(y>mid) ans+=query(id<<1|1,mid+1,r,x,y);
return ans;
}
signed main()
{
n=read(); m=read();
while(m--)
{
int op=read();
int l=read(),r=read();
if(!op) modify(1,1,n,l,r);
else printf("%lld\n",query(1,1,n,l,r));
}
return 0;
}
-
区间修改,区间查询
也是线段树基操, 附板子,C是修改区间,Q是查询
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<string>
#include<queue>
#include<iomanip>
using namespace std;
const int maxn=100005;
#define int long long
inline int read()
{
int x=0,y=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
int n,Q;
int a[maxn],tree[maxn*4];
int tag[maxn*4];
char op[2];
void push_up(int id)
{
tree[id]=tree[id<<1]+tree[id<<1|1];
return ;
}
void Add(int id,int l,int r,int val)
{
tag[id]+=val;
tree[id]+=(r-l+1)*val;
return ;
}
void build(int id,int l,int r)
{
if(l==r) {tree[id]=a[l]; return ;}
int mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
push_up(id);
return ;
}
void push_down(int id,int l,int r)
{
if(!tag[id]) return ;
int mid=(l+r)>>1;
Add(id<<1,l,mid,tag[id]);
Add(id<<1|1,mid+1,r,tag[id]);
tag[id]=0;
return ;
}
void modify(int id,int l,int r,int x,int y,int val)
{
if(x<=l&&r<=y)
{
Add(id,l,r,val);
return ;
}
int mid=(l+r)>>1;
push_down(id,l,r);
if(x<=mid) modify(id<<1,l,mid,x,y,val);
if(y>mid) modify(id<<1|1,mid+1,r,x,y,val);
push_up(id);
return ;
}
int query(int id,int l,int r,int x,int y)
{
if(x<=l&&r<=y) return tree[id];
int ans=0;
push_down(id,l,r);
int mid=(l+r)>>1;
if(x<=mid) ans+=query(id<<1,l,mid,x,y);
if(y>mid) ans+=query(id<<1|1,mid+1,r,x,y);
return ans;
}
signed main()
{
n=read(); Q=read();
for(int i=1;i<=n;++i) a[i]=read();
build(1,1,n);
while(Q--)
{
scanf("%s",op);
int a,b,c;
if(op[0]=='C')
{
a=read(); b=read();
c=read();
modify(1,1,n,a,b,c);
}
else
{
a=read(); b=read();
printf("%lld\n",query(1,1,n,a,b));
}
}
return 0;
}
-
区间最大值
含金量不高,但有变形 可以考虑转化,每次加一个数,即使假设全部是添加数,一共有T个,开个T长度的线段树,搞个计数器,那么每次就在那个位置单点修改,区间查询即可
具体看实现:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<iomanip>
#include<queue>
using namespace std;
const int maxn=200005;
const int inf=0x80000000;
inline int read()
{
int x=0,y=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
int T,mod,n,cnt(0);
int tree[maxn*4];
char op[2];
void push_up(int id)
{
tree[id]=max(tree[id<<1],tree[id<<1|1]);
return ;
}
void build(int id,int l,int r)
{
if(l==r) {tree[id]=0; return ;}
int mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
push_up(id);
return ;
}
void modify(int id,int l,int r,int pos,int val)
{
if(l==r) {tree[id]=val; return ;}
int mid=(l+r)>>1;
if(pos<=mid) modify(id<<1,l,mid,pos,val);
else modify(id<<1|1,mid+1,r,pos,val);
push_up(id);
return ;
}
int query(int id,int l,int r,int x,int y)
{
if(x<=l&&r<=y) return tree[id];
int ans=inf;
int mid=(l+r)>>1;
if(x<=mid) ans=max(query(id<<1,l,mid,x,y),ans);
if(y>mid) ans=max(ans,query(id<<1|1,mid+1,r,x,y));
return ans;
}
int pre(0);
int main()
{
T=read(); mod=read();
n=T; build(1,1,n);
while(T--)
{
scanf("%s",op);
if(op[0]=='A')
{
++cnt;
int val=read();
modify(1,1,n,cnt,(pre+val)%mod);
}
else
{
int x=read();
printf("%d\n",pre=query(1,1,n,cnt-x+1,cnt));
}
}
return 0;
}
-
区间开根号
bzoj3211原题。
属于线段树变形题,花仔很有趣♂
首先我们举个栗子,即使是1e7~1e8范围内的数,在不断开平方下取整的过程中逐渐趋近于,这时再开方仍时原数,可以进行剪枝,因此实际操作次数应该在5+次左右(1e9嘛
所以区间修改时先单点搞,复杂度不会爆,(头一次看见退化的区间修改笑死
#include<iostream>
#include<cstdio>
#include<string>
#include<cmath>
#include<string>
#include<iomanip>
#include<queue>
using namespace std;
const int maxn=100005;
typedef long long ll;
inline int read()
{
int x=0,y=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
int n,a[maxn],m;
struct node
{
ll sum,mx;
}tree[maxn*4];
void push_up(int id)
{
tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
tree[id].mx=max(tree[id<<1].mx,tree[id<<1|1].mx);
return ;
}
void build(int id,int l,int r)
{
if(l==r)
{
tree[id].sum=a[l];
tree[id].mx=tree[id].sum;
return ;
}
int mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
push_up(id);
return ;
}
void modify(int id,int l,int r,int x,int y)
{
if(tree[id].mx==1||tree[id].mx==0) return ;
if(l==r)
{
tree[id].sum=(ll)floor(sqrt(tree[id].mx));
tree[id].mx=tree[id].sum;
return ;
}
int mid=(l+r)>>1;
if(x<=mid) modify(id<<1,l,mid,x,y);
if(y>mid) modify(id<<1|1,mid+1,r,x,y);
push_up(id);
return ;
}
ll query(int id,int l,int r,int x,int y)
{
if(x<=l&&r<=y) return tree[id].sum;
int mid=(l+r)>>1;
ll ans=0;
if(x<=mid) ans+=query(id<<1,l,mid,x,y);
if(y>mid) ans+=query(id<<1|1,mid+1,r,x,y);
return ans;
}
int main()
{
n=read();
for(int i=1;i<=n;i++) a[i]=read();
build(1,1,n);
m=read();
while(m--)
{
int x,l,r;
x=read(); l=read(); r=read();
if(x==2) modify(1,1,n,l,r);
else printf("%lld\n",query(1,1,n,l,r));
}
return 0;
}
-
区间取模和区间查询
这一题可以类比上一个区间开根号,主题思想是个优化的暴力
先提出个结论的进行次数只有log次
证明显然,,只有p取m/2下取整时,得数最大
因此我们只要看一个区间什么时候不需要再进行取模,即区间最大值小于模数时,直接跳过
上代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<vector>
#include<cstdlib>
#include<queue>
using namespace std;
#define int long long
const int maxn=1e5+5;
#define lc(x) x<<1
#define rc(x) x<<1|1
#define sum(x) tree[x].sum
#define mx(x) tree[x].mx
struct seg_tree
{
int sum,mx;
}tree[maxn<<2];
inline int read()
{
int x=0,y=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
int n,m;
int val[maxn];
void push_up(int id)
{
sum(id)=sum(lc(id))+sum(rc(id));
mx(id)=max(mx(lc(id)),mx(rc(id)));
return ;
}
void build(int id,int l,int r)
{
if(l==r) {mx(id)=sum(id)=val[l]; return ;}
int mid=(l+r)>>1;
build(lc(id),l,mid);
build(rc(id),mid+1,r);
push_up(id);
return ;
}
int query(int id,int l,int r,int x,int y)
{
if(x<=l&&r<=y) return sum(id);
int mid=(l+r)>>1;
int ans=0;
if(x<=mid) ans+=query(lc(id),l,mid,x,y);
if(y>mid) ans+=query(rc(id),mid+1,r,x,y);
return ans;
}
void modify(int id,int l,int r,int x,int y,int p)
{
if(mx(id)<p) return ;
if(l==r) {sum(id)%=p; mx(id)%=p; return ;}
int mid=(l+r)>>1;
if(x<=mid) modify(lc(id),l,mid,x,y,p);
if(y>mid) modify(rc(id),mid+1,r,x,y,p);
push_up(id);
return ;
}
void update(int id,int l,int r,int pos,int val)
{
if(l==r) {sum(id)=mx(id)=val; return ;}
int mid=(l+r)>>1;
if(pos<=mid) update(lc(id),l,mid,pos,val);
else update(rc(id),mid+1,r,pos,val);
push_up(id);
return ;
}
signed main()
{
n=read(); m=read();
for(int i=1;i<=n;i++) val[i]=read();
build(1,1,n);
while(m--)
{
int opt,l,r,x;
opt=read(); l=read(); r=read();
if(opt==1) printf("%lld\n",query(1,1,n,l,r));
else if(opt==2)
{x=read(); modify(1,1,n,l,r,x);}
else update(1,1,n,l,r);
}
return 0;
}
-
区间乘法&区间加法
有几点需要注意:
- 乘法的优先级高于加法,因此优先处理乘法的懒标
- 注意 乘法和加法懒标要记得每个点都初始化,乘法tag=1;加法tag=0
- 操作过程中,不论标记是否为0我们都要下传!尤其是乘法,可能导致直接挂一半分
- 局部long long即可
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<queue>
#include<iomanip>
#include<cstdlib>
using namespace std;
typedef long long ll;
#define fabs(x) x>0?x:-x
#define register reg
inline int read()
{
int x=0,y=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
const int maxn=100005;
int p;
ll tree[maxn*4];
int n,m;
ll a[maxn];
struct node
{
ll mul,pul;
}tag[maxn*4];
void push_up(int id)
{
tree[id]=(ll)(tree[id<<1]+tree[id<<1|1])%p;
return ;
}
void build(int id,int l,int r)
{
tag[id].mul=1;
tag[id].pul=0;
if(l==r)
{
tree[id]=(ll)a[l]%p;
return ;
}
int mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
push_up(id);
return ;
}
inline void Add(int id,int l,int r,ll pls,ll mls)
{
tag[id].mul=(ll)(tag[id].mul*mls)%p;
tag[id].pul=(ll)(tag[id].pul*mls)%p;
tree[id]=(ll)(tree[id]*mls)%p;
tag[id].pul=(ll)(tag[id].pul+pls)%p;
tree[id]=(ll)(tree[id]+(r-l+1)*pls%p)%p;
return ;
}
void push_down(int id,int l,int r)
{
int mid=(l+r)>>1;
Add(id<<1,l,mid,tag[id].pul,tag[id].mul);
Add(id<<1|1,mid+1,r,tag[id].pul,tag[id].mul);
tag[id].mul=1; tag[id].pul=0;
return ;
}
void modify_mult(int id,int l,int r,int x,int y,ll val)
{
//cout<<"l: "<<l<<" "<<"r: "<<r<<endl;
if(x<=l&&r<=y) {Add(id,l,r,0,val); return ;}
push_down(id,l,r);
int mid=(l+r)>>1;
if(x<=mid) modify_mult(id<<1,l,mid,x,y,val);
if(y>mid) modify_mult(id<<1|1,mid+1,r,x,y,val);
push_up(id);
return ;
}
void modify_add(int id,int l,int r,int x,int y,ll val)
{
if(x<=l&&r<=y) {Add(id,l,r,val,1); return ;}
push_down(id,l,r);
int mid=(l+r)>>1;
if(x<=mid) modify_add(id<<1,l,mid,x,y,val);
if(y>mid) modify_add(id<<1|1,mid+1,r,x,y,val);
push_up(id);
return ;
}
ll query(int id,int l,int r,int x,int y)
{
if(x<=l&&r<=y) return tree[id]%p;
int mid=(l+r)>>1; ll ans(0);
push_down(id,l,r);
if(x<=mid) ans+=query(id<<1,l,mid,x,y)%p;
if(y>mid) ans+=query(id<<1|1,mid+1,r,x,y)%p;
return ans%p;
}
int main()
{
n=read(); p=read();
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
build(1,1,n);
m=read();
while(m--)
{
int opt=read();
if(opt==1)
{
int l,r; ll val;
l=read(); r=read(); scanf("%lld",&val);
modify_mult(1,1,n,l,r,val%p);
}
else if(opt==2)
{
int l,r; ll val;
l=read(); r=read(); scanf("%lld",&val);
modify_add(1,1,n,l,r,val%p);
}
else
{
int l,r; l=read(); r=read();
printf("%lld\n",query(1,1,n,l,r));
}
}
return 0;
}
-
区间合并类线段树
最好的例子就是这道题
简要题意就是求区间最大子段和,外加单点修改
重点就在如何维护区间上传的信息维护,这里需要四个参数,区间所有元素和,区间最大子段和,以左端元素为左端点的最大区间和,以右端元素为右端点的最大区间和
合并时考虑最大子段和是否超过区间位置,因此分两种大情况考虑
注意区间合并时的情况讨论
注意这里:坑点;答案如果覆盖区间的左右段,需要再合并一次,合并操作同pushup操作
#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<string>
using namespace std;
const int maxn=500005;
const int min_inf=0x80000000;
#define int long long
inline int read()
{
int x=0,y=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
int n,a[maxn],m;
struct node
{
int sum,dat; //dat最大子段和
int lmax,rmax;
}tree[maxn*4];
void push_up(int id)
{
int lc=id<<1,rc=id<<1|1;
tree[id].sum=tree[lc].sum+tree[rc].sum;
tree[id].lmax=max(tree[lc].lmax,tree[lc].sum+tree[rc].lmax);
tree[id].rmax=max(tree[rc].rmax,tree[rc].sum+tree[lc].rmax);
tree[id].dat=max(tree[lc].dat,tree[rc].dat);
tree[id].dat=max(tree[id].dat,tree[lc].rmax+tree[rc].lmax);
return ;
}
void build(int id,int l,int r)
{
if(l==r)
{
tree[id].sum=a[l];
tree[id].dat=tree[id].sum;
tree[id].rmax=tree[id].lmax=tree[id].sum;
return ;
}
int mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
push_up(id);
return ;
}
void modify(int id,int l,int r,int pos,int x)
{
if(l==r)
{
tree[id].sum=x;
tree[id].dat=tree[id].rmax=tree[id].lmax=x;
return ;
}
int mid=(l+r)>>1;
if(pos<=mid) modify(id<<1,l,mid,pos,x);
else modify(id<<1|1,mid+1,r,pos,x);
push_up(id);
return ;
}
node query(int id,int l,int r,int x,int y)
{
if(x<=l&&r<=y) return tree[id];
int mid=(l+r)>>1;
node ans,ta,tb;
if(y<=mid) return query(id<<1,l,mid,x,y);
if(x>mid) return query(id<<1|1,mid+1,r,x,y);
ta=query(id<<1,l,mid,x,y); tb=query(id<<1|1,mid+1,r,x,y);
ans.sum=ta.sum+tb.sum;
ans.lmax=max(ta.lmax,ta.sum+tb.lmax);
ans.rmax=max(tb.rmax,tb.sum+ta.rmax);
ans.dat=max(max(ta.dat,tb.dat),ta.rmax+tb.lmax);
return ans;
}
signed main()
{
n=read(); m=read();
for(int i=1;i<=n;i++) a[i]=read();
build(1,1,n);
while(m--)
{
int opt,x,y;
opt=read(); x=read(); y=read();
if(opt==2) modify(1,1,n,x,y);
else {if(x>y) swap(x,y); printf("%lld\n",query(1,1,n,x,y).dat);}
}
return 0;
}
-
区间gcd和区间修改
一道例题:interval GCD
膜你赛曾经打过原题,还写过题解 自卖自夸
就两个要点:
- 根据《九章算术》之更相减损术
二死了,,它扩展到多个数同时相减,这就构成了差分 - 因此构造差分序列,维护区间加只需要线段树两个单点修改,维护区间的左端点只需要用树状数组维护原差分序列,就van♂事了
代码没啥,相信各位都能手撕+吊打:
#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
#include<string>
#include<map>
#include<iomanip>
using namespace std;
const int maxn=500005;
#define fabs(x) x>0?x:-x
//typedef long long ll;
#define int long long
inline int read()
{
int x=0,y=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
int n,a[maxn];
int m;
int gcd(int a,int b)
{
if(!b) return a;
return gcd(b,a%b);
}
struct BITtree
{
int c[maxn];
int lowbit(int x) {return x&(-x);}
void add(int now,int x)
{
for(;now<=n;now+=lowbit(now)) c[now]+=x;
return ;
}
int query(int now)
{
int sum=0;
for(;now;now-=lowbit(now)) sum+=c[now];
return sum;
}
}bit;
int tree[maxn*4];
void pushup(int id)
{
tree[id]=gcd(tree[id<<1],tree[id<<1|1]);
return ;
}
void build(int id,int l,int r)
{
if(l==r)
{
tree[id]=a[l]-a[l-1];