线段树合集整理

  • 前言    

本章不是线段树教程,包含内容是博主自己刷题时的整理,请不要转载

 训练地址:一本通线段树地址  (剩余在每个板块都有链接

  • 单点修改,区间查询

没什么好说的,注意每次修改要记得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范围内的数,在不断开平方下取整的过程中逐渐趋近于\left \lfloor \sqrt{x} \right \rfloor\approx \left [ 0, 1 \right ],这时再开方仍时原数,可以进行剪枝,因此实际操作次数应该在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;
} 
  •  区间取模和区间查询

 CF438D The Child and Sequencehttps://www.luogu.com.cn/problem/CF438Dhttps://www.luogu.com.cn/problem/CF438D

这一题可以类比上一个区间开根号,主题思想是个优化的暴力 

先提出个结论m \cdot mod \cdot p \rightarrow (p<m)的进行次数只有log次

证明显然,m %p<\frac{m}{2},只有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;
} 
  • 区间乘法&区间加法

有几点需要注意:

  1. 乘法的优先级高于加法,因此优先处理乘法的懒标
  2. 注意 乘法和加法懒标要记得每个点都初始化,乘法tag=1;加法tag=0
  3. 操作过程中,不论标记是否为0我们都要下传!尤其是乘法,可能导致直接挂一半分
  4. 局部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;
}
  •  区间合并类线段树

 最好的例子就是这道题

简要题意就是求区间最大子段和,外加单点修改

重点就在如何维护区间上传的信息维护,这里需要四个参数,sum区间所有元素和,dat区间最大子段和,lmax以左端元素为左端点的最大区间和,rmax以右端元素为右端点的最大区间和

合并时考虑最大子段和是否超过区间mid位置,因此分两种大情况考虑

 注意区间合并时的情况讨论

注意这里:坑点;答案如果覆盖区间的左右段,需要再合并一次,合并操作同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

膜你赛曾经打过原题,还写过题解 自卖自夸

就两个要点:

  1. 根据《九章算术》之更相减损术 二死了,gcd(a,b)=gcd(a,b-a),它扩展到多个数同时相减,这就构成了差分
  2. 因此构造差分序列,维护区间加只需要线段树两个单点修改,维护区间的左端点只需要用树状数组维护原差分序列,就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];
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值