线段树合集

  •  前言    

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

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

  • 单点修改,区间查询

没什么好说的,注意每次修改要记得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/CF438D https://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];
		return ;
	}
	int mid=(l+r)>>1;
	build(id<<1,l,mid);
	build(id<<1|1,mid+1,r);
	pushup(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);
	pushup(id);
	return ;
}
int query(int id,int l,int r,int x,int y)
{
	if(x<=l&&r<=y) return tree[id];
	int mid=(l+r)>>1; 
	if(y<=mid) return query(id<<1,l,mid,x,y);
	if(x>mid) return query(id<<1|1,mid+1,r,x,y);
	return gcd(query(id<<1,l,mid,x,y),query(id<<1|1,mid+1,r,x,y));
}
signed main()
{
	n=read(); m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	build(1,1,n);
	while(m--)
	{
		char s[2]; int l,r,x;
		scanf("%s",s);
		if(s[0]=='Q')
		{
			l=read(); r=read();
			int new_l=a[l]+bit.query(l);
			int ans=gcd(new_l,query(1,1,n,l+1,r));
			printf("%lld\n",fabs(ans));
		}
		else
		{
			l=read(); r=read();
			x=read();
			bit.add(l,x);
			bit.add(r+1,-x);
			modify(1,1,n,l,x);
			if(r+1<=n) modify(1,1,n,r+1,-x);
		}
	}
	return 0;
}
  • 动态开点线段树 

在空间上进行节省,我们可以不建出整棵树的结构,最初只建立一个根节点,选择在某个子区间时,再建立代表这棵子树的节点

粘个板子,单点修改,维护区间最大值

#include<iostream>
using namespace std;
const int maxn=114514;
struct node
{
	int lc,rc;
	int dat; //记录信息 
}tree[maxn*2]; //具体情况分析 
int root,tot(0);
void build(int &id)
{
	id=++tot;
	tree[id].lc=tree[id].rc=0;
	tree[id].dat=0;
	return ;
}
void push_up(int id)
{
	int lson=tree[id].lc;
	int rson=tree[id].rc;
	tree[id].dat=max(tree[lson].dat,tree[rson].dat);
	return ;
}
//单点修改 
void insert(int id,int l,int r,int x,int val)
{
	if(l==r)
	{
		tree[id].dat+=val;
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid)
	{
		if(!tree[id].lc) build(tree[id].lc); //动态开点 
		insert(tree[id].lc,l,mid,x,val);
	}
	else
	{
		if(!tree[id].rc) build(tree[id].rc);
		insert(tree[id].rc,mid+1,r,x,val);
	}
	push_up(id);
	return ;
}
int n;
int main()
{
	int delta=10086,pos(0);
	tot=0;
	build(root);
	insert(root,1,n,pos,delta);
	return 0;
}
  • 线段树合并

可以看作 主席树的前身。首先考虑动态开点的线段树若干棵,它们维护相同值域 [1,n]​,假设有m​次单点修改操作,每次操作任选一颗线段树执行,所有操作完成后,我们想把这些线段树对应位置值相加,同时维护区间最大值。

这样就会用到线段树合并,通过递归合并p,q这两个代表相同区间的不同节点。

若p,q之一为空,那么返回非空那个节点;

若p,q均不为空,那么递归合并它们的左右儿子,然后删除节点q,用p替换q,自底向上更新

建议和上面代码一同食用。

void push_up(int id)
{
	int lson=tree[id].lc; int rson=tree[id].rc;
	tree[id].dat=max(tree[lson].dat,tree[rson].dat);
	return ;
}
int merge(int p,int q,int l,int r)
{
	if(!p) return q;
	if(!q) return p;
	if(l==r) {tree[p].dat+=tree[q].dat; return p;} //叶子节点
	int mid=(l+r)>>1;
	tree[p].lc=merge(tree[p].lc,tree[q].lc,l,mid);
	tree[p].rc=merge(tree[p].rc,tree[q].rc,mid+1,r);
	push_up(p);
	return p;
}

复杂度于单点修改相同,为O(m\cdot log n )

  • 权值线段树

权值线段树属于一种线段树,它的本质仍然是线段树,权值线段树的本质是线段树维护桶,桶的感性理解就是归类

概念:我们知道,普通线段树维护的信息是数列的区间信息,比如区间和、区间最大值、区间最小值等等。在维护序列的这些信息的时候,我们更关注的是这些数本身的信息,换句话说,我们要维护区间的最值或和,我们最关注的是这些数统共的信息。而权值线段树维护一列数中数的个数。 

​,举个例子

一棵权值线段树的叶子节点维护的是“有几个1”,“有几个2”...,他们的父亲节点维护的是“有几个1和2”。因此我们可以明白了,这玩意就是线段树维护的桶。

综上,我们可知:权值线段树维护的是桶,按值域开空间,维护的是个数

用途:权值线段树可以解决数列第k大/小的问题

注意:我们只能对给定数列解决整个数列的第k大/小,并不能解决数列的子区间的第k大/小

这里还有一个带图的讲解->神犇博客 

前面的建树和单点修改都和普通线段树无区别,注意建树时叶子节点权值是出现次数;

附动态开点的权值线段树查询

int query(int id,int l,int r,int val) //某个数字出现次数 
{
	if(l==r) return tree[id].cnt;
	int mid=(l+r)>>1;
	if(val<=mid) return query(tree[id].lc,l,mid,x);
	else return query(tree[id].rc,mid+1,r,x);
}
int kth_query(int id,int l,int r,int x) //区间第k小 
{ //x为剩余查找次数 
	if(l==r) return val[l];
	int mid=(l+r)>>1;
	if(x<=tree[id].cnt) return kth_query(tree[id].lc,l,mid,x);
	return kth_query(tree[id].rc,mid+1,r,x-tree[id].cnt);
}

不用动态开点的权值线段树板子:查询出现次数和第kth大数

#include<bits/stdc++.h>
using namespace std;
const int min_inf=0x80000000;
const int maxn=3e5+5;
int n,k_th;
int buc[maxn];
int tree[maxn<<2];
int maxx=min_inf;
void push_up(int id)
{
    tree[id]=tree[id<<1]+tree[id<<1|1];
    return ;
}
void build(int id,int l,int r)
{
	if(l==r) {tree[id]=buc[l]; return ;}
	int mid=(l+r)>>1;
	build(id<<1,l,mid);
	build(id<<1|1,mid+1,r);
	push_up(id);
	return ;
}
void update(int id,int l,int r,int k,int val)
{
    if(l==r)
    {
        tree[id]+=k;
        return ;
    }
    int mid=(l+r)>>1;
    if(val<=mid) update(id<<1,l,mid,k,val);
    else update(id<<1|1,mid+1,r,k,val);
    push_up(id);
    return ;
}
int query(int id,int l,int r,int val)
{
    if(l==r) return tree[id];
    int mid=(l+r)>>1;
    if(val<=mid) return query(id<<1,l,mid,val);
    else return query(id<<1|1,mid+1,r,val);
    push_up(id);
}
int query_kth(int id,int l,int r,int k)
{
	if(l==r) return l;
	int mid=(l+r)>>1;
	if(k<=tree[id<<1]) return query_kth(id<<1,l,mid,k);
	else return query_kth(id<<1|1,mid+1,r,k-tree[id<<1]);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        maxx=max(maxx,x);
        buc[x]++;
    }
    build(1,1,maxx);
    scanf("%d",&k_th);
    printf("%dth:%d\n ",k_th,query_kth(1,1,maxx,k_th));
    int m; scanf("%d",&m);
    while(m--)
    {
    	int val; scanf("%d",&val);
    	printf("%d: %d\n",val,query(1,1,maxx,val));
    }
    return 0;
}
  •   维护连通性的权值线段树

 P3224 [HNOI2012]永无乡,好题一道

题目大意:给你n个点,每个点有权值k,现有两种操作:将两个点所在联通块合并,查询某个点所在联通块权值第k小是哪个数

首先,看到权值第k小,emmm权值线段树(先不用主席树因为没用子区间,然后观察到是图论就吐了 ,因为只维护连通性,所以相当于把同一个连通块里的点都放到一个集合里,这就需要合并的过程:线段树合并get,然后你怎么确定是相连通?冰茶姬,所以只要每个点开一颗权值线段树,再动态合并,注意冰茶姬和线段树合并方向相同,就完事

还有,加个动态开点,4*n^{2}​空间hold不住啊

当然可以splay辣,只是我太蒟写不动

代码时刻:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<string>
using namespace std;
#define lc(x) tree[x].lc
#define rc(x) tree[x].rc
const int maxn=100005;
const int maxm=3200005;
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;
}
struct segtree
{
	int lc,rc,sum;
	int val;
}tree[maxm];
int n,m,q,tot(0);
int cnt(0),root[maxn],fa[maxn];
char s[6];
void push_up(int id)
{
	tree[id].sum=tree[lc(id)].sum+tree[rc(id)].sum;
	return ;
}
int get(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=get(fa[x]);
}
void modify(int &id,int l,int r,int pos,int x)
{
	if(!id) id=++tot;
	if(l==r)
	{
		tree[id].val=x;
		tree[id].sum++;
		return ;
	}
	int mid=(l+r)>>1;
	if(pos<=mid) modify(lc(id),l,mid,pos,x);
	else modify(rc(id),mid+1,r,pos,x);
	push_up(id);
	return ;
}
void merge(int &id,int p,int l,int r)
{
	if(!id) {id=p; return ;}
	if(!p) return ;
	if(l==r)
	{
		if(tree[p].val)
		{
			tree[id].val=tree[p].val;
			tree[id].sum+=tree[p].sum;
		}
		return ;
	}
	int mid=(l+r)>>1;
	merge(lc(id),lc(p),l,mid);
	merge(rc(id),rc(p),mid+1,r);
	push_up(id);
	return ;
}
int query(int id,int l,int r,int k)
{
	if(l==r) return tree[id].val;
	int mid=(l+r)>>1;
	if(k<=tree[lc(id)].sum) return query(lc(id),l,mid,k);
	else return query(rc(id),mid+1,r,k-tree[lc(id)].sum); 
}
int main()
{
	n=read(); m=read();
	for(int i=1;i<=n;i++)
	{
		fa[i]=i;
		int x=read();
		modify(root[i],1,n,x,i);
	}
	for(int i=1;i<=m;i++)
	{
		int x,y;
		x=read(); y=read();
		x=get(x); y=get(y);
		if(x==y) continue;
		fa[y]=x; merge(root[x],root[y],1,n);
	}
	q=read();
	while(q--)
	{
		scanf("%s",s+1);
		if(s[1]=='B')
		{
			int x,y;
			x=read(); y=read();
			x=get(x); y=get(y);
			if(x==y) continue;
			fa[y]=x; merge(root[x],root[y],1,n);
		}
		else
		{
			int x,k;
			x=read(); k=read();
			x=get(x);
			int ans=query(root[x],1,n,k);
			if(!ans) printf("-1\n");
			else printf("%d\n",ans);
		}
	}
	return 0;
}
  • 主席树(可持久化线段树) 

主席树一般用途就是求子区间中的第kth小,主席树中的空间利用是尽量利用曾经的线段树,再从其身上“长出”新的节点,可以观赏连体婴儿qwq 

  • 静态主席树

普通主席树 :维护区间最大值的主席树,注意可持久化线段树一般无法支持区间修改,懒标记依赖于lc和rc,但其二者关联的信息过多,很难处理

重点:主席树一般不需要pushup,具体来讲就是主席树每次修改都重新加入了一条链,无需pushup,如果pushup就改变了历史值,很惨

#include<iostream>
using namespace std;
struct node
{
	int lc,rc;
	int dat;
}tree[maxn*logn];
int tot(0),root[maxn];
int n,a[maxn];
void push_up(int id)
{
	int lson=tree[id].lc;
	int rson=tree[id].rc;
	tree[id].dat=max(tree[lson].dat,tree[rson].dat);
	return ;
}
void build(int &id,int l,int r)
{
	id=++tot;
	if(l==r) {tree[id].dat=a[l]; return ;}
	int mid=(l+r)>>1;
	build(tree[id].lc,l,mid);
	build(tree[id].rc,mid+1,r);
	push_up(id);
	return ;
}
int insert(int u,int l,int r,int x,int val)
{
	int id=++tot;
	tree[id]=tree[u];
	if(l==r) {tree[id].dat=val; return id;}
	int mid=(l+r)>>1;
	if(x<=mid) tree[id].lc=insert(tree[u].lc,l,mid,x,val);
	else tree[id].rc=insert(tree[u].rc,mid+1,r,x,val);
	push_up(id);
	return id;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",a+i);
	build(root[0],1,n);
	for(int i=1;i<=n;i++)
	{
		int x,val;
		scanf("%d%d",&x,&val);
		root[i]=insert(root[i-1],1,n,x,val);
	}
	return 0;
}

普通板子可持久化数组

注意:这里注意空间开到20倍左右,差不多是<<5酱紫

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdlib>
#include<queue>
#include<iomanip>
using namespace std;
typedef long long ll;
#define fabs(x) x>0?x:-x
const int maxn=1000005;
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;
}
struct node
{
	int lc,rc;
	ll val;
}tree[maxn*20];
int tot=0,root[maxn];
int n,m; ll a[maxn];
void build(int &id,int l,int r)
{
	id=++tot;
	tree[id].lc=tree[id].rc=0;
	tree[id].val=0;
	if(l==r)
	{
		tree[id].val=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(tree[id].lc,l,mid);
	build(tree[id].rc,mid+1,r);
	return ;
}
void modify(int &id,int p,int l,int r,int x,int y)
{
	id=++tot;
	tree[id]=tree[p];
	if(l==r) {tree[id].val=y; return ;}
	int mid=(l+r)>>1;
	if(x<=mid) modify(tree[id].lc,tree[p].lc,l,mid,x,y);
	else modify(tree[id].rc,tree[p].rc,mid+1,r,x,y);
	return ;
}
ll query(int id,int l,int r,int x)
{
	if(l==r) return tree[id].val;
	int mid=(l+r)>>1;
	if(x<=mid) return query(tree[id].lc,l,mid,x);
	else return query(tree[id].rc,mid+1,r,x);
}
int main()
{
	n=read(); m=read();
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	build(root[0],1,n);
	int x; ll y;
	for(int i=1;i<=m;i++)
	{
		int cnt,opt;
		cnt=read(); opt=read();
		if(opt==1)
		{
			x=read(); scanf("%lld",&y);
			modify(root[i],root[cnt],1,n,x,y);
		}
		else 
		{
			x=read();
			printf("%lld\n",query(root[cnt],1,n,x));
			root[i]=root[cnt];
		}
	}
	return 0;
}

有点难度的板子题地址:here 

 上图来自同机房吧神犇 ta的博客  特别巨~

我们这里只借用图。。刚才说过权值线段树统计整个区间的信息,现在,我们开n​棵线段树,每棵线段树是以时间为轴,第i​棵线段树即为从1到i​的元素加入线段树的状态。利用前缀和的思想,因为节点存放的是元素出现次数,当然满足前缀和性质,即[1,r]​内某元素出现次数为R​,[1,l-1]​内某元素出现次数为L​,则再[l,r]​区间该元素出现次数为R-L

因此这道题我们只需要将第R棵线段树减去第L-1棵线段树,就是上面讲过的合并操作,然后用权值线段树的查询扫一遍。

问题来了,既然第i棵线段树和第i+1棵只差一个元素,而单点修改只会改掉一条链,即相邻线段树只有一条log长度的链不一样

所以我们没有必要再开一颗完整的线段树,只需要在上一棵树的基础上重新增加一条链就可以了,剩下的不变的地方就沿用以前的就可以

因此我们用一个root​数组记录这n个不同线段树的根即可,每次添加一个链即可

那题有个离散化,恶心了点。

#include<iostream>
#include<queue>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
using namespace std;
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=200005;
const int maxm=200005;
int a[maxn],b[maxn];
int cnt[maxn<<5];
int lson[maxn<<5],rson[maxn<<5];
int root[maxn];
int n,m,tot;
void build(int &id,int l,int r)
{
	id=++tot;
	cnt[id]=0;
	if(l==r) return ;
	int mid=(l+r)>>1;
	build(lson[id],l,mid);
	build(rson[id],mid+1,r);
	return ;
}
int modify(int p,int l,int r,int num)
{
	int u=++tot;
	lson[u]=lson[p]; rson[u]=rson[p];
	cnt[u]=cnt[p]+1; //因为插入的hash[num]也在该区间,出现次数++
	if(l==r) return u;
	int mid=(l+r)>>1;
	if(num<=mid) lson[u]=modify(lson[u],l,mid,num);
	else rson[u]=modify(rson[u],mid+1,r,num);
	return u;
}
int query(int u,int p,int l,int r,int k)
{
	if(l==r) return l;
	int cur=cnt[lson[p]]-cnt[lson[u]];
	int mid=(l+r)>>1;
	if(cur>=k) return query(lson[u],lson[p],l,mid,k);
	else return query(rson[u],rson[p],mid+1,r,k-cur);
}
int main()
{
	n=read(); m=read();
	for(int i=1;i<=n;i++) b[i]=a[i]=read();
	sort(b+1,b+1+n);
	int now=unique(b+1,b+1+n)-b-1;
	build(root[0],1,now);
	for(int i=1;i<=n;i++)
		root[i]=modify(root[i-1],1,now,lower_bound(b+1,b+now+1,a[i])-b);
	while(m--)
    {
        int l,r,k;
        l=read(); r=read(); k=read();
        int ans=query(root[l-1],root[r],1,now,k);
        printf("%d\n",b[ans]);
    }
	return 0;
}
  •  动态主席树

需要树套树,主席树外面再套一个树状数组。但空间复杂度较高 其实是我不会  

不会啊所以没法讲,挖坑吧

俺来补坑辣

P2617 Dynamic Rankings 来到坂题

话说如果需要动态区间第k大,需要明白一点,主席树依靠前缀和性质进行查询,而如何进行前缀和的动态修改:单点修改,区间查询,就需要树状数组

因此,我们可以修改时修改pos位置的logn棵主席树,然后查询时候查r位置的logn棵树和l-1位置的logn棵树做差,注意是每个对应的树都需要做差

因此可以log平方×n的复杂度解决

上码:

#include<iostream>
#include<queue>
#include<cstring>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cstdlib>
#include<vector>
#include<cmath>
using namespace std;
const int maxn=100005;
#define lc(id) tree[id].lc
#define rc(id) tree[id].rc
typedef long long ll;
typedef pair<int,int> pii;
#define lowbit(x) x&(-x)
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;
}
struct segtree
{
	int cnt,lc,rc;
}tree[maxn*400];
char s[5];
struct ask
{
	bool opt;
	int l,r,k;
	int pos,tag;
}q[maxn];
int b[maxn<<1],len(0);
int a[maxn],m,n;
int tot(0),root[maxn];
void modify(int &id,int l,int r,int pos,int x)
{
	if(!id) id=++tot;
	tree[id].cnt+=x;
	if(l==r) return ;
	int mid=(l+r)>>1;
	if(pos<=mid) modify(lc(id),l,mid,pos,x);
	else modify(rc(id),mid+1,r,pos,x);
	return ;
}
void Modify(int pos,int x)
{
	int k=lower_bound(b+1,b+1+len,a[pos])-b;
	for(int i=pos;i<=n;i+=lowbit(i))
		modify(root[i],1,len,k,x);
	return ;
}
int t1[maxn],t2[maxn];
int c1(0),c2(0);
int query(int l,int r,int k)
{
	//cout<<l<<" "<<r<<endl;
	if(l==r) return l;
	int sum1=0,sum2=0;
	int mid=(l+r)>>1;
	for(int i=1;i<=c1;i++) sum1+=tree[lc(t1[i])].cnt;
	for(int i=1;i<=c2;i++) sum2+=tree[lc(t2[i])].cnt;
	if(k<=sum1-sum2)
	{
		for(int i=1;i<=c1;i++) t1[i]=lc(t1[i]);
		for(int i=1;i<=c2;i++) t2[i]=lc(t2[i]);
		return query(l,mid,k);
	}
	else 
	{
		for(int i=1;i<=c1;i++) t1[i]=rc(t1[i]);
		for(int i=1;i<=c2;i++) t2[i]=rc(t2[i]);
		return query(mid+1,r,k-(sum1-sum2));
	}
}
int Query(int l,int r,int k)
{
	//memset(t1,0,sizeof(t1));
	//memset(t2,0,sizeof(t2));
	c1=0; c2=0;
	for(int i=r;i;i-=lowbit(i)) t1[++c1]=root[i];
	for(int i=l-1;i;i-=lowbit(i)) t2[++c2]=root[i];
	return query(1,len,k);
}
int main()
{
	n=read(); m=read();
	for(int i=1;i<=n;i++)
		a[i]=read(),b[++len]=a[i];
	for(int i=1;i<=m;i++)
	{
		scanf("%s",s+1);
		if(s[1]=='Q')
		{
			q[i].l=read(); q[i].r=read();
			q[i].k=read(); q[i].opt=1;
		}
		else
		{
			q[i].pos=read();
			int tag=q[i].tag=read();
			b[++len]=tag; q[i].opt=0;
		}
	}
	sort(b+1,b+1+len);
	len=unique(b+1,b+1+len)-b-1;
	for(int i=1;i<=n;i++) Modify(i,1);
	for(int i=1;i<=m;i++)
	{
		if(q[i].opt)
		{
			printf("%d\n",b[Query(q[i].l,q[i].r,q[i].k)]);
			continue;
		}
		Modify(q[i].pos,-1);
		a[q[i].pos]=q[i].tag;
		Modify(q[i].pos,1);
	}
	return 0;
}

还得注意下,这题要离散化,数组开二倍,而且询问离线 

  • 树上逆序对的主席树做法

来道坂(阪题 

 具体思路:可以模拟下样例,观察到,我们可以加入k条边,那么可能出现的情况有2^{k}​种,那么可以进一步想,什么时候可以添加一条边,观察到,每个点都只能被其父节点才能更新,我们考虑这样一个情况。

​我们可以观察到,假设u和v是直接相连的两个节点,而v是w的祖先,可以是直接相连的两点。观察一下什么时候w能直接连向u,只有在w的节点编号小于v的编号时,才能保证先走到v而不是用u更新w。因此得到结论,只有u,v直接相连的情况下,只有v的子树中存在w使得可以更新v的贡献,即w的编号大于v时,贡献v+1

可以考虑到,这就是一种树上逆序对,这里提出另一种思路:dfs+树状数组维护根节点到某节点的路径上有多少节点编号小于这个节点

代码给出一种思路,主席树维护子树贡献,具体实现见码:

#include<iostream>
#include<cstdio>
#include<queue>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdlib>
#include<vector>
using namespace std;
const int maxn=2e5+5;
const int maxm=2e5+5;
const int mod=1e9+7;
#define lc(x) tree[x].lc
#define rc(x) tree[x].rc
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;
struct edge
{
	int to,next;
}g[maxm<<1];
int head[maxn],cnt(0);
inline void add(int a,int b)
{
	g[++cnt].to=b;
	g[cnt].next=head[a];
	head[a]=cnt;
	return ;
}
int dep[maxn],up[maxn];
int f[maxn];
ll quick_pow(ll a,int b)
{
	ll tmp=a%mod,ans=1;
	while(b)
	{
		if(b&1) ans=(ll)(ans*tmp)%mod;
		tmp=(tmp*tmp)%mod;
		b>>=1;
	}
	return ans%mod;
}
void dfs(int u,int fa)
{
	for(int i=head[u];i;i=g[i].next)
	{
		int v=g[i].to;
		if(v==fa) continue;
		f[v]=u;
		dfs(v,u);
	}
	return ;
}
ll ans=1;
int t(0);
void deal_60()
{
	dfs(1,0);
	for(int i=1;i<=n;i++)
	{
		int x=i; t=0;
		while(f[x])
		{
			if(f[x]<i) t++;
			x=f[x];
		}
		up[i]=t;
	}
	for(int i=2;i<=n;i++) up[i]--;
	for(int i=1;i<=n;i++)
		ans=(ll)(ans*quick_pow(2,up[i]))%mod;
	printf("%lld\n",ans);
	return ;
}
struct seg_tree
{
	int cnt,lc,rc;
}tree[maxn<<5];
int root[maxn],tot;
int lt[maxn],rt[maxn];
int rev[maxn];
void dfs1(int u,int fa)
{
	lt[u]=++t; rev[t]=u;
	for(int i=head[u];i;i=g[i].next)
	{
		int v=g[i].to;
		if(v==fa) continue;
		dfs1(v,u);
	}
	rt[u]=t;
}
void build(int &id,int l,int r)
{
	if(!id) id=++tot;
	lc(id)=rc(id)=0;
	tree[id].cnt=0;
	if(l==r) return ;
	int mid=(l+r)>>1;
	build(lc(id),l,mid);
	build(rc(id),mid+1,r);
	return ;
}
int modify(int p,int l,int r,int x)
{
	int id=++tot;
	lc(id)=lc(p); rc(id)=rc(p);
	tree[id].cnt=tree[p].cnt+1;
	if(l==r) return id;
	int mid=(l+r)>>1;
	if(x<=mid) lc(id)=modify(lc(id),l,mid,x);
	else rc(id)=modify(rc(id),mid+1,r,x);
	return id;
}
int query(int u,int v,int l,int r,int x)
{
	if(l==r) return tree[v].cnt-tree[u].cnt;
	int mid=(l+r)>>1;
	if(x<=mid) return query(lc(u),lc(v),l,mid,x)+tree[rc(v)].cnt-tree[rc(u)].cnt;
	else return query(rc(u),rc(v),mid+1,r,x);
}
void dfs2(int u,int fa)
{
	for(int i=head[u];i;i=g[i].next)
	{
		int v=g[i].to;
		if(v==fa) continue;
		up[v]+=query(root[lt[v]-1],root[rt[v]],1,n,v);
		dfs2(v,u);
	}
	return ;
}
void deal_full()
{
	tot=0; dfs1(1,0);
	build(root[0],1,n);
	for(int i=1;i<=n;i++)
		root[i]=modify(root[i-1],1,n,rev[i]);
	dfs2(1,0);
	for(int i=2;i<=n;i++) up[i]--;
	for(int i=1;i<=n;i++)
		ans=(ll)(ans*quick_pow(2,up[i]))%mod;
	printf("%lld\n",ans);
	return ;
}
int main()
{
	//freopen("dfs3.in","r",stdin);
	//freopen("dfs3.out","w",stdout);
	n=read();
	for(int i=1;i<n;i++)
	{
		int u,v;
		u=read(); v=read();
		add(u,v); add(v,u);
	}
	//if(n<=5e3) deal_60();
	//else 
	deal_full();
	return 0;
} 
/*5
1 2
1 3
2 4
2 5*/

 附官方题解:

  •  特殊区间修改主席树

看道例题,我们可以看到,这里相当于是区间修改,单点查询,众所周知,主席树不能区间修改P3168 [CQOI2015]任务查询系统xhttps://www.luogu.com.cn/problem/P3168 https://www.luogu.com.cn/problem/P3168

那怎么处理:想到用差分进行单点修改,但为什么满足正确性,因为主席树本身可以看作前缀和的桶树,这里单点查询正好利用差分一遍前缀和求出某点的答案。

因此该题变成了 单点修改,区间查询(相当于查询1~i 区间),代码如下:

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<iomanip>
using namespace std;
typedef long long ll;
const int maxn=2e5+6;
#define lc(x) tree[x].lc
#define rc(x) tree[x].rc
#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;
}
struct segtree
{
	int cnt,sum;
	int lc,rc;
}tree[maxn*20];
int root[maxn],tot(0);
int pre=1,n,m;
vector<int> segl[maxn];
vector<int> segr[maxn];
int val[maxn],b[maxn];
int len(0);
void modify(int &id,int p,int l,int r,int pos,int x)
{
	id=++tot;
	tree[id]=tree[p];
	tree[id].cnt+=x; tree[id].sum+=x*b[pos];
	if(l==r) return ;
	int mid=(l+r)>>1;
	if(pos<=mid) modify(lc(id),lc(p),l,mid,pos,x);
	else modify(rc(id),rc(p),mid+1,r,pos,x);
	return ;
}
int query(int id,int l,int r,int k)
{
	if(l==r) return tree[id].sum/tree[id].cnt*k;
	int mid=(l+r)>>1;
	if(k<=tree[lc(id)].cnt) return query(lc(id),l,mid,k);
	else return query(rc(id),mid+1,r,k-tree[lc(id)].cnt)+tree[lc(id)].sum;
} 
signed main()
{
	m=read(); n=read();
	for(int i=1;i<=m;i++)
	{
		int st,en;
		st=read(); en=read();
		b[i]=val[i]=read();
		segl[st].push_back(i);
		segr[en+1].push_back(i);
	}
	sort(b+1,b+1+m);
	len=unique(b+1,b+1+m)-b-1;
	for(int i=1;i<=n;i++)
	{
		root[i]=root[i-1];
		for(int j=0;j<segl[i].size();j++)
		{
			int pos=lower_bound(b+1,b+1+len,val[segl[i][j]])-b;
			modify(root[i],root[i],1,len,pos,1);
		}
		for(int j=0;j<segr[i].size();j++)
		{
			int pos=lower_bound(b+1,b+1+len,val[segr[i][j]])-b;
			modify(root[i],root[i],1,len,pos,-1);
		}
	}
	for(int i=1;i<=n;i++)
	{
		int x=read();
		int a=read(),b=read(),c=read();
		int k=(a*pre+b)%c+1;
		if(k>tree[root[x]].cnt) printf("%lld\n",pre=tree[root[x]].sum);
		else printf("%lld\n",pre=query(root[x],1,len,k));
	}
	return 0;
}
  •  树链剖分+树链染色

 一道题:P7735 [NOI2021] 轻重边

原题的题面讲的很清楚,这道题的具体处理就是怎么用树剖维护链上染色。我们可以考虑,打时间戳,将时间戳视作颜色,因为初始是轻边的,我们将每个点初始化为该点编号,做到互不相同。考虑每次链上覆盖,我们会修改logn个重链,而将这些重链的点权附上统一的新的时间,现在要做的,就是询问时得到链上有多少个相邻点权相等,利用线段树

注意到,为什么以上思路可行,手膜一下,我们改了链上所有点的颜色,来一条新链时,与染色过的点相连的非原链上的点一定颜色与新链上点不同,该边一定不是黑色。

所以,转变成线段树+树剖基操(雾

注意,询问时候,每个重链顶部和其父亲的边也算在该路径上,若颜色相同,记得统计。 

上码:

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const int maxm=1e5+5;
#define lc(x) x<<1
#define rc(x) x<<1|1
#define data(x) tree[x].data
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;
}
struct edge
{
	int to,next;
}g[maxm<<1];
int head[maxn],cnt(0);
int n,m,tot(0);
int val[maxn];
int son[maxn],top[maxn],seg[maxn];
int rev[maxn],dep[maxn],size[maxn];
int fa[maxn],t(0);
void init()
{
	memset(g,0,sizeof(g));
	cnt=tot=t=0;
	memset(head,0,sizeof(head));
	memset(val,0,sizeof(val));
	memset(son,0,sizeof(son));
	memset(seg,0,sizeof(seg));
	memset(size,0,sizeof(size));
	memset(rev,0,sizeof(rev));
	memset(dep,0,sizeof(dep));
	memset(fa,0,sizeof(fa));
	return ;
}
struct seg_tree
{
	int tag,lcol,rcol;
	int data;
	void init() {tag=0;}
}tree[maxn<<2];
inline void add(int a,int b)
{
	g[++cnt].to=b;
	g[cnt].next=head[a];
	head[a]=cnt;
	return ;
}
void dfs1(int u,int f)
{
	fa[u]=f; dep[u]=dep[f]+1;
	size[u]=1;
	for(int i=head[u];i;i=g[i].next)
	{
		int v=g[i].to;
		if(v==f) continue;
		dfs1(v,u);
		size[u]+=size[v];
		if(size[v]>size[son[u]]) son[u]=v;
	} 
	return ;
} 
void dfs2(int u,int p)
{
	top[u]=p;
	seg[u]=++t; rev[t]=u;
	if(son[u]) dfs2(son[u],p);
	for(int i=head[u];i;i=g[i].next)
	{
		int v=g[i].to;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
	return ;
}
void push_up(int id) 
{
	data(id)=data(lc(id))+data(rc(id));
	if(tree[lc(id)].rcol==tree[rc(id)].lcol) data(id)++;
	tree[id].lcol=tree[lc(id)].lcol;
	tree[id].rcol=tree[rc(id)].rcol;
	return ;
}
void push_down(int id,int l,int r)
{
	if(!tree[id].tag) return ;
	int mid=(l+r)>>1;
	tree[lc(id)].data=mid-l;
	tree[rc(id)].data=r-mid-1;
	tree[lc(id)].tag=tree[id].tag;
	tree[rc(id)].tag=tree[id].tag;
	tree[lc(id)].lcol=tree[lc(id)].rcol=tree[id].tag;
	tree[rc(id)].lcol=tree[rc(id)].rcol=tree[id].tag;
	tree[id].tag=0;
	return ;
}
void build(int id,int l,int r)
{
	tree[id].init();
	if(l==r)
	{
		tree[id].data=0;
		tree[id].lcol=val[rev[l]];
		tree[id].rcol=val[rev[l]];
		return ; 
	}
	int mid=(l+r)>>1;
	build(lc(id),l,mid);
	build(rc(id),mid+1,r);
	push_up(id);
	return ;
} 
void modify(int id,int l,int r,int x,int y,int col)
{
	if(x<=l&&r<=y)
	{
		tree[id].tag=col;
		tree[id].lcol=tree[id].rcol=col;
		tree[id].data=r-l;
		return ;
	}
	int mid=(l+r)>>1;
	push_down(id,l,r);
	if(x<=mid) modify(lc(id),l,mid,x,y,col);
	if(y>mid) modify(rc(id),mid+1,r,x,y,col);
	push_up(id);
	return ;
}
int query(int id,int l,int r,int x,int y)
{
	if(x<=l&&r<=y) return tree[id].data;
	push_down(id,l,r);
	int mid=(l+r)>>1,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);
	if(x<=mid&&y>mid) ans+=(tree[lc(id)].rcol==tree[rc(id)].lcol);
	return ans;
}
int quspt(int id,int l,int r,int pos)
{
	if(l==r) return tree[id].lcol;
	int mid=(l+r)>>1;
	push_down(id,l,r);
	if(pos<=mid) return quspt(lc(id),l,mid,pos);
	else return quspt(rc(id),mid+1,r,pos);
}
void change(int x,int y)
{
	++tot;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		modify(1,1,n,seg[top[x]],seg[x],tot);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	modify(1,1,n,seg[x],seg[y],tot);
	return ;
}
int ask(int x,int y)
{
	int ans(0);
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans+=query(1,1,n,seg[top[x]],seg[x]);
		int fa_topcol=quspt(1,1,n,seg[fa[top[x]]]);
		int topcol=quspt(1,1,n,seg[top[x]]);
		if(fa_topcol==topcol) ans++;
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	ans+=query(1,1,n,seg[x],seg[y]);
	return ans;
}
int T=0; 
int main()
{
	T=read();
	while(T--)
	{ 
		init();
		n=read(); m=read();
		for(int i=1;i<=n;i++) val[i]=++tot;
		for(int i=1;i<n;i++)
		{
			int u,v;
			u=read(); v=read();
			add(u,v); add(v,u);
		}
		dfs1(1,0); dfs2(1,1);
		build(1,1,n);
		while(m--)
		{
			int opt,x,y;
			opt=read(); x=read(); y=read();
			if(opt==1) change(x,y);
			else printf("%d\n",ask(x,y));
		}
	}
	return 0;
}

 附一个模拟赛考过的题,多了一步而已,找到路径长度减答案即可

 

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=3e5+5;
const int maxm=3e5+5;
#define lc(x) x<<1
#define rc(x) x<<1|1
#define data(x) tree[x].data
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;
}
struct edge
{
	int to,next;
}g[maxm<<1];
int head[maxn],cnt(0);
int n,m,tot(0);
int val[maxn];
int son[maxn],top[maxn],seg[maxn];
int rev[maxn],dep[maxn],size[maxn];
int fa[maxn],t(0);
struct seg_tree
{
	int tag,lcol,rcol;
	int data;
	void init() {tag=0;}
}tree[maxn<<2];
inline void add(int a,int b)
{
	g[++cnt].to=b;
	g[cnt].next=head[a];
	head[a]=cnt;
	return ;
}
void dfs1(int u,int f)
{
	fa[u]=f; dep[u]=dep[f]+1;
	size[u]=1;
	for(int i=head[u];i;i=g[i].next)
	{
		int v=g[i].to;
		if(v==f) continue;
		dfs1(v,u);
		size[u]+=size[v];
		if(size[v]>size[son[u]]) son[u]=v;
	} 
	return ;
} 
void dfs2(int u,int p)
{
	top[u]=p;
	seg[u]=++t; rev[t]=u;
	if(son[u]) dfs2(son[u],p);
	for(int i=head[u];i;i=g[i].next)
	{
		int v=g[i].to;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
	return ;
}
void push_up(int id) 
{
	data(id)=data(lc(id))+data(rc(id));
	if(tree[lc(id)].rcol==tree[rc(id)].lcol) data(id)++;
	tree[id].lcol=tree[lc(id)].lcol;
	tree[id].rcol=tree[rc(id)].rcol;
	return ;
}
void push_down(int id,int l,int r)
{
	if(!tree[id].tag) return ;
	int mid=(l+r)>>1;
	tree[lc(id)].data=mid-l;
	tree[rc(id)].data=r-mid-1;
	tree[lc(id)].tag=tree[id].tag;
	tree[rc(id)].tag=tree[id].tag;
	tree[lc(id)].lcol=tree[lc(id)].rcol=tree[id].tag;
	tree[rc(id)].lcol=tree[rc(id)].rcol=tree[id].tag;
	tree[id].tag=0;
	return ;
}
void build(int id,int l,int r)
{
	tree[id].init();
	if(l==r)
	{
		tree[id].data=0;
		tree[id].lcol=val[rev[l]];
		tree[id].rcol=val[rev[l]];
		return ; 
	}
	int mid=(l+r)>>1;
	build(lc(id),l,mid);
	build(rc(id),mid+1,r);
	push_up(id);
	return ;
} 
void modify(int id,int l,int r,int x,int y,int col)
{
	if(x<=l&&r<=y)
	{
		tree[id].tag=col;
		tree[id].lcol=tree[id].rcol=col;
		tree[id].data=r-l;
		return ;
	}
	int mid=(l+r)>>1;
	push_down(id,l,r);
	if(x<=mid) modify(lc(id),l,mid,x,y,col);
	if(y>mid) modify(rc(id),mid+1,r,x,y,col);
	push_up(id);
	return ;
}
int query(int id,int l,int r,int x,int y)
{
	if(x<=l&&r<=y) return tree[id].data;
	push_down(id,l,r);
	int mid=(l+r)>>1,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);
	if(x<=mid&&y>mid) ans+=(tree[lc(id)].rcol==tree[rc(id)].lcol);
	return ans;
}
int quspt(int id,int l,int r,int pos)
{
	if(l==r) return tree[id].lcol;
	int mid=(l+r)>>1;
	push_down(id,l,r);
	if(pos<=mid) return quspt(lc(id),l,mid,pos);
	else return quspt(rc(id),mid+1,r,pos);
}
void change(int x,int y)
{
	++tot;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		modify(1,1,n,seg[top[x]],seg[x],tot);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	modify(1,1,n,seg[x],seg[y],tot);
	return ;
}
int lca(int x,int y)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	return x;
}
int dis(int x,int y)
{return dep[x]+dep[y]-2*dep[lca(x,y)];}
int ask(int x,int y)
{
	int ans(0);
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans+=query(1,1,n,seg[top[x]],seg[x]);
		int fa_topcol=quspt(1,1,n,seg[fa[top[x]]]);
		int topcol=quspt(1,1,n,seg[top[x]]);
		if(fa_topcol==topcol) ans++;
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	ans+=query(1,1,n,seg[x],seg[y]);
	return ans;
}
int main()
{
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++) val[i]=++tot;
	for(int i=1;i<n;i++)
	{
		int u,v;
		u=read(); v=read();
		add(u,v); add(v,u);
	}
	dfs1(1,0); dfs2(1,1);
	build(1,1,n);
	m=read();
	while(m--)
	{
		int opt,x,y;
		opt=read(); x=read(); y=read();
		if(opt==1) change(x,y);
		else printf("%d\n",dis(x,y)-ask(x,y));
	}
	return 0;
}
  • 树链剖分+动态开点线段树

例题地址:P3313 [SDOI2014]旅行 

原题需要u,v​两点间路径,树剖是要敲的,其次,对于宗教种数,我们每个宗教建一颗线段树,会炸空间,因此考虑动态开点,对于改教信的这个城市,将原教清空,在插入新教

注意下细节,然后就是各种基操。。。

上代码,开饭:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<cmath>
#include<queue>
#include<algorithm>
#include<iomanip>
#include<cstdlib>
using namespace std;
const int maxn=100005;
const int maxm=100005;
#define min_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;
}
struct edge
{
	int to,next;
}g[maxm<<1];
int head[maxn],cnt=0;
struct segtree
{
	int sum,maxv;
	int lc,rc;
}tree[maxn*40];
inline void add(int a,int b)
{
	g[++cnt].to=b;
	g[cnt].next=head[a];
	head[a]=cnt;
	return ;
}
int root[maxn];
int n,q;
int son[maxn],fa[maxn],top[maxn];
int dep[maxn],size[maxn];
int seg[maxn],rev[maxn],tot(0);
int w[maxn],c[maxn],t=0;
void dfs1(int u,int f)
{
	size[u]=1; fa[u]=f;
	dep[u]=dep[f]+1;
	for(int i=head[u];i;i=g[i].next)
	{
		int v=g[i].to;
		if(v==f) continue;
		dfs1(v,u);
		size[u]+=size[v];
		if(size[v]>size[son[u]]) son[u]=v;
	}
	return ;
}
void dfs2(int u,int p)
{
	seg[u]=++tot; rev[tot]=u;
	top[u]=p;
	if(son[u]) dfs2(son[u],p);
	for(int i=head[u];i;i=g[i].next)
	{
		int v=g[i].to;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
	return ;
}
void build(int &id,int l,int r)
{
	id=++t;
	tree[id].maxv=tree[id].sum=0;
	return ;
}
void push_up(int id)
{
	int lson=tree[id].lc;
	int rson=tree[id].rc;
	tree[id].sum=tree[lson].sum+tree[rson].sum;
	tree[id].maxv=max(tree[lson].maxv,tree[rson].maxv);
	return ;
}
inline void update(int &id,int l,int r,int pos,int x)
{
	if(!id) id=++t;
	//tree[id].maxv=max(tree[id].maxv,x);
	//tree[id].sum+=x;
	if(l==r)
	{
		tree[id].sum+=x;
		tree[id].maxv=x;
		return ;
	}
	int mid=(l+r)>>1;
	if(pos<=mid) 
	{
		if(!tree[id].lc) build(tree[id].lc,l,mid);
		update(tree[id].lc,l,mid,pos,x);
	}
	else
	{
		if(!tree[id].rc) build(tree[id].rc,mid+1,r);
		update(tree[id].rc,mid+1,r,pos,x);
	}
	push_up(id);
	return ;
}
void delete_rt(int id,int l,int r,int pos)
{
	if(l==r)
	{
		tree[id].maxv=tree[id].sum=0;
		return ;
	}
	int mid=(l+r)>>1;
	if(pos<=mid) delete_rt(tree[id].lc,l,mid,pos);
	else delete_rt(tree[id].rc,mid+1,r,pos);
	push_up(id);
	return ;
}
int query_tot(int id,int l,int r,int x,int y)
{
	if(x<=l&&r<=y) return tree[id].sum;
	int mid=(l+r)>>1;
	int ans=0;
	if(x<=mid) ans+=query_tot(tree[id].lc,l,mid,x,y);
	if(y>mid) ans+=query_tot(tree[id].rc,mid+1,r,x,y);
	return ans;
}
int ask_tot(int rt,int x,int y)
{
	int ans=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans+=query_tot(root[rt],1,n,seg[top[x]],seg[x]);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	ans+=query_tot(root[rt],1,n,seg[x],seg[y]);
	return ans;
}
int query_max(int id,int l,int r,int x,int y)
{
	if(x<=l&&r<=y) return tree[id].maxv;
	int mid=(l+r)>>1;
	int ans=min_inf;
	if(x<=mid) ans=max(ans,query_max(tree[id].lc,l,mid,x,y));
	if(y>mid) ans=max(ans,query_max(tree[id].rc,mid+1,r,x,y));
	return ans;
}
int ask_max(int rt,int x,int y)
{
	int ans=min_inf;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans=max(ans,query_max(root[rt],1,n,seg[top[x]],seg[x]));
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	ans=max(ans,query_max(root[rt],1,n,seg[x],seg[y]));
	return ans;
}
char s[20];
int main()
{
	//freopen("P3313_1.in","r",stdin);
	//freopen("P3313_1.ans","w",stdout);
	n=read(); q=read();
	for(int i=1;i<=n;i++) w[i]=read(),c[i]=read();
	int x,y;
	for(int i=1;i<n;i++)
	{
		x=read(); y=read();
		add(x,y); add(y,x);
	}
	dfs1(1,0); dfs2(1,1);
	for(int i=1;i<=n;i++) update(root[c[i]],1,n,seg[i],w[i]);
	while(q--)
	{
		scanf("%s",s+1);
		x=read(); y=read();
		switch(s[2])
		{
            case 'C':
			{
                delete_rt(root[c[x]],1,n,seg[x]);
                update(root[y],1,n,seg[x],w[x]);
                c[x]=y; break;
            }
            case 'W':
			{
                delete_rt(root[c[x]],1,n,seg[x]);
                update(root[c[x]],1,n,seg[x],y);
                w[x]=y; break;
            }
            case 'S': {printf("%d\n",ask_tot(c[x],x,y)); break;}
            case 'M': {printf("%d\n",ask_max(c[x],x,y)); break;}
        }
	}
	return 0;
}
  •  带反转的01串的线段树

原题:P2572 [SCOI2010]序列操作 

这时候考虑懒标记的恶心之处,分tag​和rev​反转 标记两种懒标,tag初始-1,因为要表示区间全部覆盖成0或者1,所以迫不得已。。

区间最长连续1串就用最大子段和那道题的思想来完成,记三个量,mx,lm,rm,分别表示以左端点开头,右端点开头的最长1串,合并时候就很好想了,这里有重点,0,1的三个量都要记录,因为会有反转,1,0全部交换即可

而tag会依赖区间长度总和,那我们记一个sum,如果tag全1或全0,意味着反转一下tag就好,但记得也要反转01;而tag不为全一或全0,rev记录反转状态,pushdown时候进行反转就好,要是每下推就改了rev就血赚

实现如下:

貌似我代码可读性还不错的,不多解释了

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<iomanip>
#include<ctime>
using namespace std;
const int maxn=100005;
#define lc id<<1
#define rc id<<1|1
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;
}
struct segtree
{
	int lm[2],rm[2];
	int mx[2];
	int sum[2],tag,rev;
	void swp(int t)
	{
		swap(lm[t],lm[t^1]); swap(rm[t],rm[t^1]);
		swap(mx[t],mx[t^1]);
		swap(sum[t],sum[t^1]);
		return ;
	}
	void init() {tag=-1; rev=0;}
}tree[maxn<<2];
int a[maxn],n,m;
void push_up(int id,int l,int r)
{
	for(int i=0;i<=1;i++)
	{
		tree[id].sum[i]=tree[lc].sum[i]+tree[rc].sum[i];
		tree[id].lm[i]=tree[lc].lm[i];
		tree[id].rm[i]=tree[rc].rm[i];
		int mid=(l+r)>>1;
		if(tree[lc].lm[i]==mid-l+1) tree[id].lm[i]+=tree[rc].lm[i];
		if(tree[rc].rm[i]==r-mid) tree[id].rm[i]+=tree[lc].rm[i];
		tree[id].mx[i]=max(tree[lc].mx[i],tree[rc].mx[i]);
		tree[id].mx[i]=max(tree[id].mx[i],tree[lc].rm[i]+tree[rc].lm[i]);
	}
	return ;
}
void seqcov(int id,int l,int r)
{
	int t=tree[id].tag;
	tree[id].sum[t]=tree[id].lm[t]=tree[id].rm[t]=r-l+1;
	tree[id].mx[t]=r-l+1;
	tree[id].sum[t^1]=tree[id].lm[t^1]=tree[id].rm[t^1]=tree[id].mx[t^1]=0;
	return ;
}
void push_down(int id,int l,int r)
{
	if(tree[id].rev)
	{
		if(tree[lc].tag!=-1) tree[lc].tag^=1;
		else tree[lc].rev^=1;
		tree[lc].swp(0);
		if(tree[rc].tag!=-1) tree[rc].tag^=1;
		else tree[rc].rev^=1;
		tree[rc].swp(0);
		tree[id].rev=0;
	}
	if(tree[id].tag!=-1) 
	{
		tree[lc].tag=tree[rc].tag=tree[id].tag;
		int mid=(l+r)>>1;
		seqcov(lc,l,mid); seqcov(rc,mid+1,r);
		tree[id].tag=-1;
	}
	return ;
}
void build(int id,int l,int r)
{
	tree[id].init();
	if(l==r)
	{
		tree[id].sum[1]=(a[l]==1);
		tree[id].sum[0]=(a[l]==0);
		tree[id].lm[1]=tree[id].rm[1]=tree[id].mx[1]=a[l];
		tree[id].lm[0]=tree[id].rm[0]=tree[id].mx[0]=1-a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(id<<1,l,mid);
	build(id<<1|1,mid+1,r);
	push_up(id,l,r);
	return ;
}
void modify(int id,int l,int r,int x,int y,int opt)
{
	if(x<=l&&r<=y)
	{
		tree[id].tag=opt;
		tree[id].rev=0;
		seqcov(id,l,r);
		return ;
	}
	push_down(id,l,r);
	int mid=(l+r)>>1;
	if(x<=mid) modify(id<<1,l,mid,x,y,opt);
	if(y>mid) modify(id<<1|1,mid+1,r,x,y,opt);
	push_up(id,l,r);
	return ;
}
void reverse(int id,int l,int r,int x,int y)
{
	if(x<=l&&r<=y)
	{
		if(tree[id].tag!=-1) tree[id].tag^=1;
		else tree[id].rev^=1;
		tree[id].swp(1);
		return ;
	}
	push_down(id,l,r);
	int mid=(l+r)>>1;
	if(x<=mid) reverse(id<<1,l,mid,x,y);
	if(y>mid) reverse(id<<1|1,mid+1,r,x,y);
	push_up(id,l,r);
	return ;
}
int query_sum(int id,int l,int r,int x,int y)
{
	if(x<=l&&r<=y) return tree[id].sum[1];
	push_down(id,l,r);
	int mid=(l+r)>>1,ans=0;
	if(x<=mid) ans+=query_sum(lc,l,mid,x,y);
	if(y>mid) ans+=query_sum(rc,mid+1,r,x,y);
	return ans;
}
struct poi
{
	int len,lm,rm,mx;
	poi() {};
	poi(int p,int q,int r,int s) {len=p,lm=q,rm=r,mx=s;}
};
poi query(int id,int l,int r,int x,int y)
{
	if(x<=l&&r<=y)
	{
		poi tmp=poi(r-l+1,tree[id].lm[1],tree[id].rm[1],tree[id].mx[1]);
		return tmp;
	}
	push_down(id,l,r);
	int mid=(l+r)>>1;
	if(y<=mid) return query(lc,l,mid,x,y);
	else if(x>mid) return query(rc,mid+1,r,x,y);
	else
	{
		poi tmp1=query(lc,l,mid,x,y);
		poi tmp2=query(rc,mid+1,r,x,y);
		poi ans=poi(tmp1.len+tmp2.len,tmp1.lm,tmp2.rm,max(tmp1.mx,tmp2.mx));
		if(tmp1.lm==tmp1.len) ans.lm+=tmp2.lm;
		if(tmp2.rm==tmp2.len) ans.rm+=tmp1.rm;
		ans.mx=max(ans.mx,tmp1.rm+tmp2.lm);
		return ans;
	}
}
int main()
{
	n=read(); m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	build(1,1,n);
	while(m--)
	{
		int op,l,r;
		op=read(); l=read(); r=read();
		l++; r++;
		if(op==1||op==0) modify(1,1,n,l,r,op);
		else if(op==2) reverse(1,1,n,l,r);
		else if(op==3) printf("%d\n",query_sum(1,1,n,l,r));
		else printf("%d\n",query(1,1,n,l,r).mx);
	}
	return 0;
}
/*10 10
0 0 0 1 1 0 1 0 1 1
1 0 2
3 0 5
2 2 2
4 0 4
0 3 6
2 3 7
4 2 8
1 0 5
0 5 6
3 3 9*/
  • 以时间为轴建立线段树

这种思想实在妙蛙,可以解决很多看似无解的情况

一道例题:P4588 [TJOI2018]数学计算

考虑大暴力,逆元不能除法,van♂蛋

那就换一种思路,看到Q是1e5级别的,来活了

我们以询问时间为轴,建立线段树

叶子结点维护该操作时间的乘数,非叶子结点维护

区间乘,叶子结点一开始都为1,然后每次乘,树的根节点就是答案。进行单点修改,将该次操作时间的位置修改为该乘数,每次除的话,就将询问的操作位置的乘数改为1

然后就基操: 

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<string>
#include<vector>
#include<iomanip>
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;
}
ll tree[maxn*4];
int mod,n;
void push_up(int id)
{
	tree[id]=(ll)(tree[id<<1]*tree[id<<1|1])%mod;
	return ;
}
void build(int id,int l,int r)
{
	if(l==r)
	{
		tree[id]=1;
		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==pos&&l==r)
	{
		if(!x) tree[id]=1;
		else 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 x) {return tree[x]%mod;}
int main()
{
	int T;
	T=read();
	while(T--)
	{
		memset(tree,0,sizeof(tree));
		n=read(); mod=read();
		build(1,1,n);
		for(int i=1;i<=n;i++)
		{
			int op,x;
			op=read(); x=read();
			if(op==1) modify(1,1,n,i,x);
			else modify(1,1,n,x,0);
			printf("%d\n",query(1));
		}
	}
	return 0;
}
  • 01线段树和二分答案  

P2824 [HEOI2016/TJOI2016]排序

又一道神题 

亿点反思:像这种题目,若与大小关系有关,可以尝试枚举断点,将其转化为01串搞就完了 

示例 代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
const int maxn=100005;
int n,m,p;
int tree[maxn*4],lazy[maxn*4];
int a[maxn],ch[maxn],l[maxn],r[maxn];
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; 
}
void build(int id,int l,int r,int x)
{
	if(l==r)
	{
		tree[id]=(a[l]>=x);
		lazy[id]=0;
		return ;
	}
	int mid=(l+r)>>1;
	build(id<<1,l,mid,x);
	build(id<<1|1,mid+1,r,x);
	tree[id]=tree[id<<1]+tree[id<<1|1];
	lazy[id]=0;
	return ; 
}
void pushdown(int id,int l,int r)
{
	if(!lazy[id]) return ;
	lazy[id<<1]=lazy[id<<1|1]=lazy[id];
	int mid=(l+r)>>1;
	if(lazy[id]==1) tree[id<<1]=mid-l+1,tree[id<<1|1]=r-mid;
	else tree[id<<1]=tree[id<<1|1]=0;
	lazy[id]=0;
	return ;
}
int query(int id,int l,int r,int x,int y)
{
	if(x<=l&&y>=r) return tree[id];
	if(x>r||y<l) return 0;
	pushdown(id,l,r);
	int mid=(l+r)>>1;
	return query(id<<1,l,mid,x,y)+query(id<<1|1,mid+1,r,x,y);
}
int querypoint(int id,int l,int r,int x)
{
	if(l==x&&l==r) return tree[id];
	pushdown(id,l,r);
	int mid=(l+r)>>1;
	if(x<=mid) return querypoint(id<<1,l,mid,x);
	else return querypoint(id<<1|1,mid+1,r,x);
}
void update(int id,int l,int r,int x,int y,int val)
{
	if(x<=l&&y>=r)
	{
		tree[id]=val*(r-l+1);
		lazy[id]=val?1:-1;
		return ;
	}
	if(x>r||y<l) return ;
	pushdown(id,l,r);
	int mid=(l+r)>>1;
	update(id<<1,l,mid,x,y,val);
	update(id<<1|1,mid+1,r,x,y,val);
	tree[id]=tree[id<<1]+tree[id<<1|1];
	return ;
}
inline bool check(int x)
{
	build(1,1,n,x);
	for(int i=1;i<=m;i++)
	{
		int tmp=query(1,1,n,l[i],r[i]);
		if(!ch[i]) 
		{
			update(1,1,n,r[i]-tmp+1,r[i],1);
			update(1,1,n,l[i],r[i]-tmp,0);
		}
		else
		{
			update(1,1,n,l[i],l[i]+tmp-1,1);
			update(1,1,n,l[i]+tmp,r[i],0);
		}
	}
	return querypoint(1,1,n,p);
}
int main()
{
	n=read(); m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=m;i++)
	{
		ch[i]=read();
		l[i]=read(); r[i]=read();
	}
	p=read();
	int L=1,R=n,ans;
	while(L<=R)
	{
		int MID=(L+R)>>1;
		if(check(MID))
		{
			ans=MID;
			L=MID+1;
		}
		else R=MID-1;
	}
	printf("%d",ans);
	return 0;
}
  • 区间覆盖类线段树

P1712 [NOI2016] 区间

一道好题,首先数据范围大,离散一下,其次很显然是维护区间,用线段树,我们可以抽象认为,每添加一条线段,是将[l,r]区间的数加一,那么区间修改就完事

那按什么顺序添加?考虑两个问题,有至少一个点覆盖次数大于等于m,和要求最长和最短区间差最小,第一个问题直接维护区间最大值,不小于m即可;而显然我们想要差尽量小,那么很常规地想到按照区间长度排个序,之后这里提供两个思路

1.二分答案,由于区间花费有单调性,越向后花费越多,可以将当前区间定为最大值,将小于当前区间len-mid的区间弹出,尝试通过check是否有最大值大于等于m,复杂度n套个log平方海星能拿80;

2.尺取法,这玩意啥意思我也不知道,可以问问度娘,但显然问题转化成一个长度小于等于n的滑动窗口,扫一下里面总区间是否合法,然后合法就尝试把左端指针向右移,因为差越小越好,直到不再合法,更新答案,总的来看也算是二分答案检验的一种思想,只不过每次更新一个区间就好

代码:(试着自己打一遍

#include<iostream>
#include<queue>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<string>
#include<iomanip>
using namespace std;
typedef long long ll;
#define inf 0x7fffffff
#define minf 0x80000000
const int maxn=1000005;
#define tag(x) tree[x].tag
#define lc(x) x<<1
#define rc(x) x<<1|1
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;
struct nodeseg
{
	int l,r,len;
}g[maxn];
struct seg_tree
{
	int sum,tag,maxv;
	void init()
	{
		sum=tag=maxv=0;
		return ;
	}
}tree[maxn<<2];
int num[maxn],cnt(0);
bool cmp(nodeseg a,nodeseg b) {return a.len<b.len;}
void push_up(int id)
{
	tree[id].maxv=max(tree[lc(id)].maxv,tree[rc(id)].maxv);
	return ;
}
void push_down(int id)
{
	if(!tag(id)) return ;
	tree[lc(id)].maxv+=tree[id].tag;
	tree[rc(id)].maxv+=tree[id].tag;
	tree[lc(id)].tag+=tree[id].tag;
	tree[rc(id)].tag+=tree[id].tag;
	tree[id].tag=0;
	return ;
}
void modify(int id,int l,int r,int x,int y,int val)
{
	if(x<=l&&r<=y)
	{
		tree[id].tag+=val;
		tree[id].maxv+=val;
		return ;
	}
	push_down(id);
	int mid=(l+r)>>1;
	if(x<=mid) modify(lc(id),l,mid,x,y,val);
	if(y>mid) modify(rc(id),mid+1,r,x,y,val);
	push_up(id);
	return ;
}
int main()
{
	n=read(); m=read();
	int l,r;
	for(int i=1;i<=n;i++)
	{
		l=g[i].l=read(); r=g[i].r=read();
		g[i].len=r-l+1;
		num[++cnt]=l,num[++cnt]=r;
	}
	sort(num+1,num+1+cnt);
	cnt=unique(num+1,num+1+cnt)-num-1;
	sort(g+1,g+1+n,cmp);
	l=inf; r=minf;
	for(int i=1;i<=n;i++)
	{
		g[i].l=lower_bound(num+1,num+1+cnt,g[i].l)-num;
		g[i].r=lower_bound(num+1,num+1+cnt,g[i].r)-num;
		l=min(l,g[i].l); r=max(r,g[i].r);
	}
	int ans=inf;
	int now=1;
	for(int i=1;i<=n;i++)
	{
		modify(1,l,r,g[i].l,g[i].r,1);
		while(tree[1].maxv>=m)
		{
			ans=min(ans,g[i].len-g[now].len);
			modify(1,l,r,g[now].l,g[now].r,-1);
			now+=1;
		}
	}
	if(ans!=inf) printf("%d",ans);
	else printf("%d",-1);
	return 0;
}
  • 二维线段树 

举个例子: 

维护操作:区间更新和查询 

(单点的修改和查询也简单,修改直接在叶子节点上加到tag上就可,查询每次统计加上当前区间的tag值就可以。

来一道例题:poj2155 Matrix

这是一个线段树的二维区间更新问题,由于这里只有单点查询,所以只需要在查询的过程中加上经过的所有的永久化标记即可。 

两种做法,一种可以区间修改每次加1,最后因为是单点查询,加上标记值就可,然后求和mod 2

还有一种是每次异或1,最后单点查询就是异或和

俺写的是直接异或:

#include<iostream>
#include<queue>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<string>
#include<iomanip>
using namespace std;
typedef long long ll;
const int maxn=1005;
#define inf 0x7fffffff
#define minf 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 n,q;
int tree[maxn<<2][maxn<<2];
char s[5];
void modify_y(int id,int idx,int l,int r,int y1,int y2)
{
    if(y1<=l&&r<=y2)
    {
        tree[idx][id]^=1;
        return ;
    }
    int mid=(l+r)>>1;
    if(y1<=mid) modify_y(id<<1,idx,l,mid,y1,y2);
    if(y2>mid) modify_y(id<<1|1,idx,mid+1,r,y1,y2);
    return ;
}
void modify_x(int id,int l,int r,int x1,int y1,int x2,int y2)
{
    if(l==r)
    {
        modify_y(1,id,1,n,y1,y2);
        return ;
    }
    int mid=(l+r)>>1;
    if(x1<=mid) modify_x(id<<1,l,mid,x1,y1,x2,y2);
    if(x2>mid) modify_x(id<<1|1,mid+1,r,x1,y1,x2,y2);
    return ;
}
int ans=0;
void query_y(int id,int idx,int l,int r,int y)
{
    ans^=tree[idx][id];
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(y<=mid) query_y(id<<1,idx,l,mid,y);
    else query_y(id<<1|1,idx,mid+1,r,y);
    return ;
}
void query_x(int id,int l,int r,int x,int y)
{
    query_y(1,id,1,n,y);
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(x<=mid) query_x(id<<1,l,mid,x,y);
    else query_x(id<<1|1,mid+1,r,x,y);
}
int main()
{
    freopen("matrix.in","r",stdin);
    freopen("matrix.out","w",stdout);
    n=read(); q=read();
    int x1,y1,x2,y2;
    while(q--)
    {
        scanf("%s",s+1);
        if(s[1]=='C')
        {
            x1=read(); y1=read();
            x2=read(); y2=read();
            modify_x(1,1,n,x1,y1,x2,y2);
        }
        else
        {
            x1=read(); y1=read();
            ans=0;
            query_x(1,1,n,x1,y1);
            printf("%d\n",ans);
        }
    }
	return 0;
}
  •  线段树标记永久化

除了下传标记,我们还有一种选择,即在询问过程中计算每个遇到的节点对当前询问的影响。为保证询问的复杂度,子节点的影响需要在修改操作时就计算好

​ 一维线段树标记永久化

int comlen(int l,int r,int x,int y) {return min(r,y)-max(l,x)+1;}
//comlen函数是求两个区间的交集长度
void modify(int id,int l,int r,int x,int y,int val)
{
	tree[id].sum+=comlen(l,r,x,y)*val;
	if(x<=l&&r<=y)
	{
		tree[id].tag+=val;
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid) modify(id<<1,l,mid,x,y,val);
	if(y>mid) modify(id<<1|1,mid+1,r,x,y,val);
	return ;
}
int 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,ans(0);
	ans=comlen(l,r,x,y)*tree[id].tag;
	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;
}
  • 树套树

数据结构套数据结构,什么牛马,没事,已经会了的二维线段树也算线段树套线段树。。

首先看个例子:

​ 声明一下,这里有修改操作,单点更新,所以不能离线

话说能离线就可以按照k从小到大排序,随k增大,将区间扫一下,小于等于k的位置加一,然后区间统计和值即可。总的修改是nlog(n),查询是qlog(n),所以1e5没问题

而有修改

 emmm,原理可以理解,代码实在写不出QAQ(鸽

  •  线段树和期望

话说期望本人一直都有点懵,不知道怎么求,还好,依照最简单的 期望=情况权值和*情况出现概率还能做题

P2221 [HAOI2012]高速公路

分析一下:首先原题是链且区间修改,不用树剖,直接线段树,把每一条路的花费作为终点处的点的权值就好了​ 

原式可化为: 

 展开:

 然后线段树具体处理几个区间维护变量即可,代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<queue>
#include<set>
using namespace std;
//typedef long long ll;
typedef pair<int,int> pii;
#define lc(id) id<<1
#define rc(id) id<<1|1
#define tag(id) tree[id].tag
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,m;
struct segtree
{
	int sum1,sum2,sum3;
	int sum4,sum5;
	int tag;
}tree[maxn<<2];
inline void push_up(int id)
{
	tree[id].sum1=tree[lc(id)].sum1+tree[rc(id)].sum1;
    tree[id].sum2=tree[lc(id)].sum2+tree[rc(id)].sum2;
    tree[id].sum3=tree[lc(id)].sum3+tree[rc(id)].sum3;
    return ;
}
void update(int id)
{
	tree[id].sum4=tree[lc(id)].sum4+tree[rc(id)].sum4;
    tree[id].sum5=tree[lc(id)].sum5+tree[rc(id)].sum5;
    return ;
}
void push_down(int id,int l,int r)
{
	if(!tree[id].tag) return ;
	int mid=(l+r)>>1;
	tree[lc(id)].tag+=tag(id); tree[rc(id)].tag+=tag(id);
    tree[lc(id)].sum1+=(mid-l+1)*tag(id); tree[rc(id)].sum1+=(r-mid)*tag(id);
    tree[lc(id)].sum2+=tag(id)*tree[lc(id)].sum4;
	tree[rc(id)].sum2+=tag(id)*tree[rc(id)].sum4;
    tree[lc(id)].sum3+=tag(id)*tree[lc(id)].sum5;
	tree[rc(id)].sum3+=tag(id)*tree[rc(id)].sum5;
	tree[id].tag=0;
    return ;
}
void build(int id,int l,int r)
{
	if(l==r)
	{
		tree[id].sum4=l;
		tree[id].sum5=l*l;
		return ;
	}
	int mid=(l+r)>>1;
	build(lc(id),l,mid);
	build(rc(id),mid+1,r);
	update(id);
	return ;
}
void modify(int id,int l,int r,int x,int y,int val)
{
	if(x<=l&&r<=y)
	{
		tree[id].sum1+=(r-l+1)*val;
		tree[id].sum2+=val*tree[id].sum4;
        tree[id].sum3+=val*tree[id].sum5;
		tree[id].tag+=val;
        return ;
	}
	push_down(id,l,r);
	int mid=(l+r)>>1;
	if(x<=mid) modify(lc(id),l,mid,x,y,val);
	if(y>mid) modify(rc(id),mid+1,r,x,y,val);
	push_up(id);
	return ;
}
int t1,t2,t3;
void query(int id,int l,int r,int x,int y)
{
    if(x<=l&&r<=y)
	{
		t1+=tree[id].sum1;
		t2+=tree[id].sum2; t3+=tree[id].sum3;
		//cout<<t1<<" "<<t2<<" "<<t3<<endl;
		return;
	}
    push_down(id,l,r);
    int mid=(l+r)>>1;
    if(x<=mid) query(lc(id),l,mid,x,y);
	if(y>mid) query(rc(id),mid+1,r,x,y);
	return ;
}
char s[5];
int gcd(int a,int b)
{
	if(!b) return a;
	return gcd(b,a%b);
}
signed main()
{
	n=read(); m=read();
	build(1,1,n);
	while(m--)
	{
		scanf("%s",s);
		int lt=read(),rt=read();
		if(s[0]=='C')
		{
			int x=read();
			if(lt==rt) continue;
			modify(1,1,n,lt+1,rt,x);
		}
		else
		{
			t1=0,t2=0,t3=0;
			query(1,1,n,lt+1,rt);
			//cout<<t1<<" "<<t2<<" "<<t3<<endl;
			int fz=-t3+(lt+rt+1)*t2+(rt-(lt+1)-(lt+1)*rt+1)*t1;
			int fm=(rt-lt+1)*(rt-lt)/2;
			int gans=gcd(fz,fm);
			printf("%lld/%lld\n",fz/gans,fm/gans);
		}
	}
	return 0;
}

记着约分!

  • 扫描线求面积并 

扫描线的思想很简单,我们可以 想想把一个不规则性质,内角只含直角的图形,分割成几个矩形。粘个图:

image0a03aa15aca4877e.png

 参考下:这是从下往上扫的扫描线,它按照纵轴排序,以下标线段建树,过程很好理解。

再粘个图:这是从左向右扫的过程

酱紫

而对于如何建树,有个很重要的关键点。以从下向上扫为例:就是横坐标线段的划分 我们知道线段树划分的与其说区间,不如说是从l~r位置编号的点,而这里想用线段树加速,就要想下线段树维护的含义。

举个栗子:[1,3]区间,线段树划分为:[1,2],[3,3];这样的话后者是个点,整个线段的总长度就错误变成2了,所以对于这种情况,我们把整个x轴坐标离散化分成tot个点,那用tot-1个叶子区间代替,每个区间[l,r]代表着x轴上的[xt[l],xt[r+1]];这样如[3,3]的区间就分成了线段树上的[1,1]和[2,2];我们这样做,是确保每个区间有意义,因此只要建区间在[1,tot-1]范围内的线段树即可

P5490 【模板】扫描线https://www.luogu.com.cn/problem/P5490 https://www.luogu.com.cn/problem/P5490洛谷的板子题,代码如下:

注意这里求的是区间覆盖总长度,要是一个区间被覆盖了,长度就是实际长度,防止覆盖后算重。

#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
#include<string>
#include<iomanip>
#include<cstdio>
using namespace std;
#define lowbit(x) x&(-x)
const int maxn=1000005;
#define lc(x) x<<1
#define rc(x) x<<1|1
#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;
}
struct segtree
{
	int len,cnt;
}tree[maxn*8];
int n,tot(0);
struct scanline
{
	int l,r,h,mark;
}line[maxn<<3];
int xt[maxn<<3];
bool cmp(scanline a,scanline b) {return a.h<b.h;}
void build(int id,int l,int r)
{
	tree[id].len=0;
	tree[id].cnt=0;
	if(l==r) return ;
	int mid=(l+r)>>1;
	build(id<<1,l,mid);
	build(id<<1|1,mid+1,r);
	return ;
}
void push_up(int id,int l,int r)
{
	if(tree[id].cnt) tree[id].len=(xt[r+1]-xt[l]);
	else tree[id].len=tree[lc(id)].len+tree[rc(id)].len;
	return ;
}
void modify(int id,int l,int r,int x,int y,int val)
{
	if(x<=xt[l]&&xt[r+1]<=y)
	{
		tree[id].cnt+=val;
		push_up(id,l,r);
		return ;
	}
	int mid=(l+r)>>1;
	if(x<xt[mid+1]) modify(lc(id),l,mid,x,y,val);//无法取等原因: 
	//假设现在考虑 [2,5], [5,8] 两条线段,要修改 [1,5] 区间的sum
	//很明显,虽然5在这个区间内,[5,8] 却并不是我们希望修改的线段 
	if(y>xt[mid+1]) modify(rc(id),mid+1,r,x,y,val);
	push_up(id,l,r);
	return ;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		int x1,y1,x2,y2;
		x1=read(); y1=read();
		x2=read(); y2=read();
		xt[i*2-1]=x1; xt[i*2]=x2;
		line[i*2-1]=(scanline){x1,x2,y1,1};
		line[i*2]=(scanline){x1,x2,y2,-1};
	}
	n*=2;
	sort(line+1,line+1+n,cmp);
	sort(xt+1,xt+1+n);
	tot=unique(xt+1,xt+1+n)-xt-1;
	build(1,1,tot-1);
	int ans=0;
	for(int i=1;i<n;i++)
	{
		modify(1,1,tot-1,line[i].l,line[i].r,line[i].mark);
		ans+=tree[1].len*(line[i+1].h-line[i].h);
	}
	printf("%ll",ans);
	return 0;
} 
  • 扫描线处理覆盖问题

看一道好题: 

P1502 窗口的星星https://www.luogu.com.cn/problem/P1502 https://www.luogu.com.cn/problem/P1502

 这位巨佬的讲解很到位,这里我补充下自己的想法。

这里是求点的覆盖,不像上面那道面积并那么恶心,所以直接不用处理区间的问题,直接用线段树处理点就好

代码如下:

#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
#include<string>
#include<iomanip>
#include<cstdio>
using namespace std;
#define lowbit(x) x&(-x)
const int maxn=1e5+5;
#define lc(x) x<<1
#define rc(x) x<<1|1
#define mx(x) tree[x].maxv
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;
}
struct scan
{
	int l,r,h;
	int val;
	bool operator <(const scan &x) const
	{
		if(h-x.h) return h<x.h;
		return val>x.val;
	}
}line[maxn<<1];
struct segtree
{
	int l,r;
	int tag,maxv;
}tree[maxn<<2];
int n,w,h;
int yt[maxn<<1];
void init()
{
	memset(line,0,sizeof(line));
	memset(tree,0,sizeof(tree));
	return ;
}
void push_up(int id)
{
	mx(id)=max(mx(lc(id)),mx(rc(id)));
	return ;
}
void build(int id,int l,int r)
{
	mx(id)=tree[id].tag=0;
	if(l==r) return ;
	int mid=(l+r)>>1;
	build(lc(id),l,mid);
	build(rc(id),mid+1,r);
	push_up(id);
	return ;
}
void push_down(int id)
{
	if(!tree[id].tag) return ;
	tree[lc(id)].tag+=tree[id].tag;
	tree[rc(id)].tag+=tree[id].tag;
	mx(lc(id))+=tree[id].tag;
	mx(rc(id))+=tree[id].tag;
	tree[id].tag=0;
	return ; 
}
void modify(int id,int l,int r,int x,int y,int val)
{
	if(x<=l&&r<=y)
	{
		mx(id)+=val;
		tree[id].tag+=val;
		return ;
	}
	push_down(id);
	int mid=(l+r)>>1;
	if(x<=mid) modify(lc(id),l,mid,x,y,val);
	if(y>mid) modify(rc(id),mid+1,r,x,y,val);
	push_up(id);
	return ;
}
int T(0),ans=0;
int main()
{
	T=read();
	while(T--)
	{
		init();
		ans=0;
		n=read(); w=read();
		h=read();
		for(int i=1;i<=n;i++)
		{
			int x,y,val;
			x=read(); y=read();
			val=read();
			yt[i*2-1]=y; yt[i*2]=y+h-1;
			line[i*2-1]=(scan){y,y+h-1,x,val};
			line[i*2]=(scan){y,y+h-1,x+w-1,-val};
		}
		n*=2;
		sort(yt+1,yt+1+n);
		sort(line+1,line+1+n);
		int tot=unique(yt+1,yt+1+n)-yt-1;
		build(1,1,tot);
		for(int i=1;i<=n;i++)
		{
			line[i].l=lower_bound(yt+1,yt+1+tot,line[i].l)-yt;
			line[i].r=lower_bound(yt+1,yt+1+tot,line[i].r)-yt;
			//cout<<line[i].l<<" "<<line[i].r<<endl;
		}
		for(int i=1;i<=n;i++)
		{
			modify(1,1,tot,line[i].l,line[i].r,line[i].val);
			ans=max(ans,tree[1].maxv);
		}
		printf("%d\n",ans);
	}
	return 0;
} 
  • 区间异或问题 

见到异或序列问题,我们可以考虑三种情况

  1. 异或前缀和 
  2. 01字典树
  3. 逐位拆分+线段树
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<cstdlib>
#include<iomanip>
#include<cmath>
using namespace std;
#define lc(x) x<<1
#define rc(x) x<<1|1
const int maxn=1e5+5;
const int maxk=25;
#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,val[maxn];
int base[maxn];
struct segtree
{
	int f[maxk];
	int tag[maxk];
}tree[maxn<<2]; int ans(0);
void push_up(int id,int pos)
{
	tree[id].f[pos]=tree[lc(id)].f[pos]+
	tree[rc(id)].f[pos];
	return ;
}
void build(int id,int l,int r)
{
	if(l==r)
	{
		for(int i=0;i<=20;i++)
			tree[id].f[i]=((val[l]>>i)&1);
		return ;
	}
	int mid=(l+r)>>1;
	build(lc(id),l,mid);
	build(rc(id),mid+1,r);
	for(int i=0;i<=20;i++) push_up(id,i);
	return ;
}
void push_down(int id,int l,int r)
{
	int mid=(l+r)>>1;
	for(int i=0;i<=20;i++)
	{
		if(!tree[id].tag[i])
			continue;
		tree[lc(id)].f[i]=(mid-l+1)-tree[lc(id)].f[i];
		tree[rc(id)].f[i]=(r-mid)-tree[rc(id)].f[i];
		tree[lc(id)].tag[i]^=1;
		tree[rc(id)].tag[i]^=1;
		tree[id].tag[i]=0;
	}
	return ;
}
void modify(int id,int l,int r,int x,int y,int pos)
{
	if(x<=l&&r<=y)
	{
		tree[id].f[pos]=(r-l+1)-tree[id].f[pos]; 
		tree[id].tag[pos]^=1;
		return ;
	}
	push_down(id,l,r);
	int mid=(l+r)>>1;
	if(x<=mid) modify(lc(id),l,mid,x,y,pos);
	if(y>mid) modify(rc(id),mid+1,r,x,y,pos);
	push_up(id,pos);
	return ;
}
void query(int id,int l,int r,int x,int y)
{
	if(x<=l&&r<=y)
	{
		for(int i=0;i<=20;i++)
			ans+=tree[id].f[i]*base[i];
		return ;
	}
	push_down(id,l,r);
	int mid=(l+r)>>1;
	if(x<=mid) query(lc(id),l,mid,x,y);
	if(y>mid) query(rc(id),mid+1,r,x,y);
	return ;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++) val[i]=read();
	build(1,1,n); base[0]=1;
	for(int i=1;i<=20;i++) 
		base[i]=base[i-1]<<1;
	int T=read();
	while(T--)
	{
		int opt,l,r;
		opt=read();
		l=read(); r=read();
		if(opt==1)
		{
			ans=0; query(1,1,n,l,r);
			printf("%lld\n",ans);
			continue;
		}
		int x=read();
		for(int i=0;i<=20;i++)
			if((x>>i)&1) modify(1,1,n,l,r,i);
	}
	return 0;
}
  • 支持覆盖区间加的历史最大值线段树

P4314 CPU监控 https://www.luogu.com.cn/problem/P4314来到好题,显然的维护法是设立一个his,专门求历史最大值

仔细思考发现,这样做不行因为标记未下传就可能被覆盖

​  

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<string> 
#include<queue>
#include<cstdio>
#include<cstdlib>
#include<bitset>
using namespace std;
typedef long long ll;
#define lc(x) x<<1
#define rc(x) x<<1|1
#define mx(x) tree[x].mx
#define his(x) tree[x].his
#define tag(x) tree[x].tag
#define htag(x) tree[x].htag
const int maxn=1e5+5;
const int inf=-0x3f3f3f3f;
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 tmp[maxn];
char s[maxn];
struct seg_tree
{
	int his,mx;
	int htag,tag;
	bool chg;
	int hx,chx;
}tree[maxn<<2];
void push_up(int id)
{
	mx(id)=max(mx(id<<1),mx(id<<1|1));
	his(id)=max(his(lc(id)),his(rc(id)));
	return ;
}
void Add(int id,int tg,int htg)
{
	his(id)=max(his(id),mx(id)+htg);
	mx(id)+=tg;
	if(tree[id].chg)
	{
		tree[id].chx=max(tree[id].chx,tree[id].hx+htg);
		tree[id].hx+=tg;
		//这里我们把覆盖后的标记打在chx和hx
		//上,可以证明正确性,覆盖后的加在覆盖值上 
	}
	else
	{
		htag(id)=max(htag(id),tag(id)+htg);
		tag(id)+=tg;
	}
	return ;
}
void cover(int id,int tg,int htg)
{
	his(id)=max(his(id),htg);
	mx(id)=tg;
	tree[id].chg=1;
	tree[id].chx=max(tree[id].chx,htg);
	tree[id].hx=tg;
	return ;
}
void push_down(int id)
{
	if(tag(id)||htag(id))
	{
		Add(lc(id),tag(id),htag(id));
		Add(rc(id),tag(id),htag(id));
		tag(id)=htag(id)=0;
	}
	if(tree[id].chg)
	{
		cover(lc(id),tree[id].hx,tree[id].chx);
		cover(rc(id),tree[id].hx,tree[id].chx);
		tree[id].chg=0;
		tree[id].chx=tree[id].hx=inf;
	}
	return ;
}
int query(int id,int l,int r,int x,int y)
{
	if(x<=l&&r<=y) return his(id);
	push_down(id);
	int mid=(l+r)>>1,ret=inf;
	if(x<=mid) ret=max(ret,query(lc(id),l,mid,x,y));
	if(y>mid) ret=max(ret,query(rc(id),mid+1,r,x,y));
	return ret;
}
int query_now(int id,int l,int r,int x,int y)
{
	if(x<=l&&r<=y) return mx(id);
	push_down(id);
	int mid=(l+r)>>1,ret=inf-1;
	if(x<=mid) ret=max(ret,query_now(lc(id),l,mid,x,y));
	if(y>mid) ret=max(ret,query_now(rc(id),mid+1,r,x,y));
	return ret;
}
void modify(int id,int l,int r,int x,int y,int w)
{
	if(x<=l&&r<=y)
	{
		Add(id,w,w);
		return ;
	}
	push_down(id);
	int mid=(l+r)>>1;
	if(x<=mid) modify(lc(id),l,mid,x,y,w);
	if(y>mid) modify(rc(id),mid+1,r,x,y,w);
	push_up(id);
	return ;
}
void change(int id,int l,int r,int x,int y,int w)
{
	if(x<=l&&r<=y)
	{
		cover(id,w,w);
		return ;
	}
	int mid=(l+r)>>1;
	push_down(id);
	if(x<=mid) change(lc(id),l,mid,x,y,w);
	if(y>mid) change(rc(id),mid+1,r,x,y,w);
	push_up(id);
	return ;
}
void build(int id,int l,int r)
{
	tag(id)=htag(id)=0;
	tree[id].chg=0;
	tree[id].hx=inf;
	tree[id].chx=inf;
	if(l==r)
	{
		mx(id)=his(id)=tmp[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(id<<1,l,mid);
	build(id<<1|1,mid+1,r);
	push_up(id);
	return ;
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++) tmp[i]=read();
	build(1,1,n);
	m=read();
	for(int i=1;i<=m;i++)
	{
		scanf("%s",s+1);
		if(s[1]=='A')
		{
			int l,r;
			l=read(); r=read();
			printf("%d\n",query(1,1,n,l,r));
		}
		else if(s[1]=='Q')
		{
			int l,r;
			l=read(); r=read();
			printf("%d\n",query_now(1,1,n,l,r));
		}
		else if(s[1]=='P')
		{
			int l,r,w;
			l=read(); r=read(),w=read();
			modify(1,1,n,l,r,w);
		}
		else if(s[1]=='C')
		{
			int l,r,x;
			l=read(); r=read(),x=read();
			change(1,1,n,l,r,x);
		}
	}
	return 0;
}
/*10
-62 -83 -9 -70 79 -78 -31 40 -18 -5 
20
A 2 7
A 4 4
Q 4 4
P 2 2 -74
P 7 9 -71
P 7 10 -8
A 10 10
A 5 9
C 1 8 10
Q 6 6
Q 8 10
A 1 7
P 9 9 96
A 5 5
P 8 10 -53
P 6 6 5
A 10 10
A 4 4
Q 1 5
P 4 9 -69*/
  • 线段树分治

https://www.luogu.com.cn/problem/P6109

ynoi。。望而止步(?

话说泽巨巨还认为这是ynoi之耻,俺还搞了三天 我tcl

线段树分治的主要原理就是利用一颗横向线段树来优化一维的问题

具体还是看题解吧

关键有几个:

首先把问题拆分为行上差分,将二维问题差分为两次区间修改

然后维护区间修改,维护区间历史最大值

之后保证每次询问在行上的连续区间过mid,否则给左右儿子

最后一个线段树,维护每一行跑,保证时间复杂度

https://www.luogu.com.cn/blog/EnderManWaper/solution-p6109

  1 #include<iostream>
  2 #include<cstring>
  3 #include<algorithm>
  4 #include<vector>
  5 #include<cmath>
  6 #include<string> 
  7 #include<queue>
  8 #include<cstdio>
  9 #include<cstdlib>
 10 #include<bitset>
 11 using namespace std;
 12 const int maxn=5e5+5;
 13 #define int long long 
 14 typedef long long ll;
 15 #define lc(x) x<<1
 16 #define rc(x) x<<1|1 
 17 #define mx(x) tree[x].mx
 18 #define his(x) tree[x].his
 19 #define tag1(x) tree[x].tag1
 20 #define tag2(x) tree[x].tag2
 21 inline int read()
 22 {
 23     int x=0,y=1; char c=getchar();
 24     while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
 25     while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
 26     return x*y;
 27 }
 28 int n,m,q,ans[maxn];
 29 struct ask
 30 {
 31     int id,l,r,x,y;
 32 }a[maxn];
 33 struct node {int l,r,val;};
 34 vector<node> vec[maxn];
 35 vector<ask> que[maxn*4];
 36 inline bool cmp(node a,node b)
 37 {return a.val<b.val;}
 38 struct seg_tree
 39 {
 40     int mx,his;
 41     int tag1,tag2,res;
 42 }tree[maxn<<2];
 43 void Add(int id,int tg1,int tg2)
 44 {
 45     his(id)=max(his(id),mx(id)+tg2);
 46     mx(id)+=tg1;
 47     tag2(id)=max(tag1(id)+tg2,tag2(id));
 48     tag1(id)+=tg1;
 49     return ;
 50 }
 51 void push_up(int id)
 52 {
 53     mx(id)=max(mx(id<<1),mx(id<<1|1));
 54     his(id)=max(his(lc(id)),his(rc(id)));
 55     return ;
 56 }
 57 void resist(int id)
 58 {
 59     Add(lc(id),tag1(id),tag2(id));
 60     Add(rc(id),tag1(id),tag2(id));
 61     his(id)=mx(id); tag1(id)=tag2(id)=0;
 62     tree[id].res=1;
 63     return ;
 64 }
 65 void push_down(int id)
 66 {
 67     if(tree[id].res)
 68     {
 69         resist(lc(id));
 70         resist(rc(id));
 71         tree[id].res=0;
 72     }
 73     Add(lc(id),tag1(id),tag2(id));
 74     Add(rc(id),tag1(id),tag2(id));
 75     tag1(id)=tag2(id)=0;
 76     return ;
 77 }
 78 void modify(int id,int l,int r,int x,int y,int w)
 79 {
 80     if(x<=l&&r<=y) {Add(id,w,w); return ;}
 81     push_down(id);
 82     int mid=(l+r)>>1;
 83     if(x<=mid) modify(lc(id),l,mid,x,y,w);
 84     if(y>mid) modify(rc(id),mid+1,r,x,y,w);
 85     push_up(id);
 86     return ;
 87 }
 88 int query(int id,int l,int r,int x,int y)
 89 {
 90     if(x<=l&&r<=y) return his(id);
 91     push_down(id);
 92     int mid=(l+r)>>1,ret(0);
 93     if(x<=mid) ret=max(ret,query(lc(id),l,mid,x,y));
 94     if(y>mid) ret=max(ret,query(rc(id),mid+1,r,x,y));
 95     return ret;
 96 }
 97 void insert(int x,int opt)
 98 {
 99     if(opt==1)
100     {
101         for(int i=0;i<vec[x].size();i++)
102             modify(1,1,n,vec[x][i].l,
103             vec[x][i].r,vec[x][i].val);
104     }
105     else
106     {
107         int tmp=vec[x].size();
108         for(int i=tmp-1;i>=0;i--)
109             modify(1,1,n,vec[x][i].l,
110             vec[x][i].r,-vec[x][i].val);
111     }
112     return ;
113 }
114 void add(int id,int l,int r,ask p)
115 {
116     int mid=(l+r)>>1;
117     if(p.l<=mid+1&&p.r>=mid)
118     {que[id].push_back(p); return ;}
119     if(p.r<=mid) add((lc(id)),l,mid,p);
120     else add(rc(id),mid+1,r,p);
121     return ;
122 } 
123 bool cmp1(ask a,ask b)
124 {return a.r<b.r;}
125 bool cmp2(ask a,ask b)
126 {return a.l>b.l;}
127 void binary_solve(int x,int l,int r)
128 {
129     int mid=(l+r)>>1;
130     for(int i=l;i<=mid;i++) insert(i,1);
131     int cnt(0),u=1;
132     for(int i=0;i<que[x].size();i++)
133         a[++cnt]=que[x][i];
134     sort(a+1,a+cnt+1,cmp1);
135     while(u<=cnt&&a[u].r==mid) u++;
136     for(int i=mid+1;i<=r;i++)
137     {
138         insert(i,1);
139         if(i==mid+1) resist(1);
140         while(a[u].r==i&&u<=cnt)
141         {
142             ans[a[u].id]=max(ans[a[u].id],
143             query(1,1,n,a[u].x,a[u].y));
144             u++;
145         }
146     }
147     for(int i=r;i>=mid+1;i--) insert(i,-1);
148     if(l^r) binary_solve(x<<1|1,mid+1,r);
149     cnt=0,u=1;
150     for(int i=0;i<que[x].size();i++)
151         a[++cnt]=que[x][i];
152     sort(a+1,a+cnt+1,cmp2);
153     while(u<=cnt&&a[u].l==mid+1) u++;
154     for(int i=mid;i>=l;i--)
155     {
156         if(i==mid) resist(1);
157         while(a[u].l==i&&u<=cnt)
158         {
159             ans[a[u].id]=max(ans[a[u].id],
160             query(1,1,n,a[u].x,a[u].y));
161             u++;
162         }
163         insert(i,-1);
164     }
165     if(l^r) binary_solve(x<<1,l,mid);
166     return ;
167 }
168 signed main()
169 {
170     n=read(); m=read(); q=read();
171     for(int i=1;i<=m;i++)
172     {
173         int l,r,x,y,w;
174         l=read(); x=read();
175         r=read(); y=read(); w=read();
176         vec[l].push_back((node){x,y,w});
177         vec[r+1].push_back((node){x,y,-w});
178     }
179     for(int i=1;i<=q;i++)
180     {
181         int l,r,x,y;
182         l=read(); x=read();
183         r=read(); y=read();
184         ask tmp=(ask){i,l,r,x,y};
185         add(1,1,n,tmp);
186     }
187     for(int i=1;i<=n;i++)
188         sort(vec[i].begin(),vec[i].end(),cmp);
189     binary_solve(1,1,n);
190     for(int i=1;i<=q;i++)
191         printf("%lld\n",ans[i]);
192     return 0;
193 }

 

posted @ 2022-01-13 20:09  Mastey  阅读(6)  评论(0)    收藏  举报