线段树可以实现很多的操作。这里就一些最基本的操作进行讨论。
线段树是通过区间合并和lazy标记作为核心来实现log级别的操作的。
区间合并是核心中的核心,你所要支持的操作需要转换成可以进行区间合并的形式。
这里讨论了一些常见操作的维护+区间合并的题目。
数据结构A题
基本上维护和以及平方和,支持赋值、增、乘的操作。
多个操作需要注意优先级:赋值*乘+增。
pushdown的时候,赋值覆盖所有操作,所有子节点的操作恢复初始值。乘会对加标记有影响。
【推断:高优先级对低优先级有影响】
update的时候,再加上这个操作的时候,判断这个操作有没有低优先级的,如果有pushdown再继续。
比如:(a+b)*c,b的标记还没有传下去,加上c的话,会导致变成a*c+b。【因为我们规定了pushdown时计算的优先级】
唯一要注意的是平方和的计算需要列下公式,(a+k)^2,(a*k)^2展开即可知道。
#include<bits/stdc++.h>
#define FOR(i,l,r) for(int i=l;i<=r;i++)
#define ll long long
#define maxn 500050
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
struct tree2{
tree2 *lson,*rson;
ll x,x_2,lazyt=inf,lazyp=0,lazym=1;
}dizhi[maxn<<2],*root=&dizhi[0];
ll n,m,t=1;
ll a[maxn];
ll P=1000000000+7;
void push_up(tree2 *tree,ll l,ll r){
tree->x=(tree->lson->x+tree->rson->x)%P;
tree->x_2=(tree->lson->x_2+tree->rson->x_2)%P;
}
void build(tree2 *tree,ll l,ll r){
if(l==r){
tree->x=a[l];
tree->x_2=(a[l]*a[l])%P;
return ;
}
ll mid=(l+r)>>1;
tree->lson=&dizhi[t++];
tree->rson=&dizhi[t++];
build(tree->lson,l,mid);
build(tree->rson,mid+1,r);
push_up(tree,l,r);
}
void pushdown(tree2 *tree,ll l,ll r){
//cout<<"*"<<endl;
if((tree->lazyt==inf&&tree->lazyp==0&&tree->lazym==1)||tree->lson==NULL)return ;
ll mid=(l+r)>>1;//我们规定优先级=覆盖(A)*B+C->这样会比较方便修改
if(tree->lazyt!=inf){
tree->lson->x=(tree->lazyt*(mid-l+1))%P,tree->rson->x=(tree->lazyt*(r-mid))%P;
tree->lson->x_2=(mid-l+1)*((tree->lazyt*tree->lazyt)%P)%P;
tree->rson->x_2=(r-mid)*((tree->lazyt*tree->lazyt)%P)%P;//覆盖
tree->lson->lazyt=tree->rson->lazyt=tree->lazyt;
tree->lson->lazym=tree->rson->lazym=1;
tree->lson->lazyp=tree->rson->lazyp=0;
}//之前子标记说明在之前被修改,一旦遇到现在才传递过来的新覆盖标记,毫无作用了直接清空。
tree->lson->x=(tree->lson->x*tree->lazym)%P,tree->rson->x=(tree->rson->x*tree->lazym)%P;
tree->lson->x_2=((tree->lson->x_2*tree->lazym)%P*tree->lazym)%P,tree->rson->x_2=((tree->rson->x_2*tree->lazym)%P*tree->lazym)%P;//*
tree->lson->x_2=(tree->lson->x_2+(ll)2*tree->lazyp*tree->lson->x%P+(mid-l+1)*(tree->lazyp*tree->lazyp%P)%P)%P;
tree->rson->x_2=(tree->rson->x_2+(ll)2*tree->lazyp*tree->rson->x%P+(r-mid)*(tree->lazyp*tree->lazyp%P)%P)%P;
//这两行和下面一行实现的是最后的加,(a+k)^2=a^2+2ak+k^2=>a^2和+2k*a和+l*k^2,这里a和是没加之前a和,所以要放前面
tree->lson->x=(tree->lson->x+tree->lazyp*(mid-l+1)%P)%P,tree->rson->x=(tree->rson->x+tree->lazyp*(r-mid)%P)%P;
//下面是:传递标记
tree->lson->lazym=(tree->lson->lazym*tree->lazym)%P;
tree->rson->lazym=(tree->rson->lazym*tree->lazym)%P;
tree->lson->lazyp=(tree->lson->lazyp*tree->lazym%P+tree->lazyp)%P;
tree->rson->lazyp=(tree->rson->lazyp*tree->lazym%P+tree->lazyp)%P;
tree->lazyt=inf;
tree->lazyp=0;
tree->lazym=1;
}
void change0(tree2 *tree,ll l,ll r,ll x,ll y,ll d){
if(x<=l&&y>=r){
tree->lazyt=d;
tree->lazym=1;
tree->lazyp=0;
tree->x=d*(r-l+1)%P;
tree->x_2=(r-l+1)*(d*d%P)%P;
return ;
}
pushdown(tree,l,r);
ll mid=(l+r)>>1;
if(x<=mid)change0(tree->lson,l,mid,x,y,d);
if(y>mid)change0(tree->rson,mid+1,r,x,y,d);
push_up(tree,l,r);
}
void change1(tree2 *tree,ll l,ll r,ll x,ll y,ll d){
if(x<=l&&y>=r){
//cout<<tree->x<<endl;
if(tree->lazyp)pushdown(tree,l,r);//优先级越位,(覆盖*x),如果再*y=>覆盖*(x*y)无影响,如果覆盖*x+k,不传递下去的话,会变成(覆盖*(x*y)+k)错误
tree->x=(tree->x*d)%P;
//cout<<tree->x<<endl;
tree->x_2=(tree->x_2*((d*d)%P))%P;
//cout<<tree->x_2<<endl;
tree->lazym=(tree->lazym*d)%P;
return ;
}
pushdown(tree,l,r);
int mid=(l+r)>>1;
if(x<=mid)change1(tree->lson,l,mid,x,y,d);
if(y>mid)change1(tree->rson,mid+1,r,x,y,d);
push_up(tree,l,r);
}
void change2(tree2 *tree, ll l,ll r,ll x,ll y,ll d){
if(x<=l&&y>=r){
tree->x_2=(tree->x_2+(ll)2*tree->x*d%P+((r-l+1)*(d*d%P))%P)%P;
//cout<<tree->x_2<<endl;
tree->x=(tree->x+(ll)d*(r-l+1))%P;
//cout<<tree->x<<endl;
tree->lazyp=(tree->lazyp+(ll)d)%P;
return ;
}
pushdown(tree,l,r);
int mid=(l+r)>>1;
if(x<=mid)change2(tree->lson,l,mid,x,y,d);
if(y>mid)change2(tree->rson,mid+1,r,x,y,d);
push_up(tree,l,r);
}
ll query_x(tree2 *tree,ll l,ll r,ll x,ll y){
if(x<=l&&y>=r)return tree->x%P;
pushdown(tree,l,r);
int mid=(l+r)>>1;
ll t1=0,t2=0;
if(x<=mid)t1=query_x(tree->lson,l,mid,x,y);
if(y>mid)t2=query_x(tree->rson,mid+1,r,x,y);
return (t1+t2)%P;
}
ll query_x2(tree2 *tree,ll l,ll r,ll x,ll y){
if(x<=l&&y>=r)return tree->x_2%P;
pushdown(tree,l,r);
int mid=(l+r)>>1;
ll t1=0,t2=0;
if(x<=mid)t1=query_x2(tree->lson,l,mid,x,y);
if(y>mid)t2=query_x2(tree->rson,mid+1,r,x,y);
return (t1+t2)%P;
}
int main(){
scanf("%lld%lld",&n,&m);
FOR(i,1,n)scanf("%lld",&a[i]);
build(root,1,n);
FOR(i,1,m){
int mode;
ll a,b;
ll c;
scanf("%d",&mode);
if(mode==1){
scanf("%lld%lld%lld",&a,&b,&c);
change2(root,1,n,a,b,c);
}
if(mode==2){
scanf("%lld%lld%lld",&a,&b,&c);
change1(root,1,n,a,b,c);
}
if(mode==3){
scanf("%lld%lld%lld",&a,&b,&c);
change0(root,1,n,a,b,c);
}
if(mode==4){
scanf("%lld%lld",&a,&b);
ll t1=query_x(root,1,n,a,b);
ll t2=query_x2(root,1,n,a,b);
//printf("%lld %lld\n",t1,t2);
printf("%lld\n",((b-a+1)*t2%P-t1*t1%P+P)%P);
}
}
return 0;
}
区间合并好题 HDU1540
对于这个求最长连续1的操作,我们考虑区间合并的时候(pushup),maxlen=a:左孩子左端点开始的最长、b:右孩子右端点开始的最长,c:左孩子右端点开始的+右孩子左端点的最长中最大的。
像这样:1 1 0 1 1 1 0 1 1 1 1 1 1 0 0 1 1 1 0 0 1 1
a=2,b=4+2,c=2
询问操作的时候:需要注意的是,|____x___| |_______|
对于子区间,ll,lr,rl,rr(左孩子左端点开始有多长,左孩子右端点往左连续的有多长)
如果x在lr中了,或者在rl中了,直接返回lr+rl。
否则继续往下,知道最后只有一个点返回这个点的值。【一般到最后的都是0】
#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
using namespace std;
#define M 500050
struct tree2{
tree2 *lson,*rson;
int fl,fr,ml;
}dizhi[M<<1],*root=&dizhi[0];
int n,m,t=1,a[M];
int Mmax(int a,int b,int c){
return max(max(a,b),c);
}
void pushup(tree2 *tree,int l,int r){
int a=tree->lson->fl,b=tree->lson->fr+tree->rson->fl,c=tree->rson->fr;
tree->ml=Mmax(a,b,c);
tree->fl=tree->lson->fl;
tree->fr=tree->rson->fr;
int mid=(l+r)>>1;
if(tree->lson->fl==mid-l+1)tree->fl+=tree->rson->fl;
if(tree->rson->fr==r-mid)tree->fr+=tree->lson->fr;
//cout<<tree->lson->fl<<" "<<tree->lson->fr<<" "<<tree->rson->fl<<" "<<tree->rson->fr<<endl;
//cout<<tree->ml<<endl;
}
void build(tree2 *tree,int l,int r){
if(l==r){
tree->ml=tree->fl=tree->fr=r-l+1;
return ;
}
int mid=(l+r)>>1;
tree->lson=&dizhi[t++];
tree->rson=&dizhi[t++];
build(tree->lson,l,mid);
build(tree->rson,mid+1,r);
pushup(tree,l,r);
}
void update(tree2 *tree,int l,int r,int x,int cnt){
if(l==r){
tree->ml=tree->fl=tree->fr=cnt;
return ;
}
int mid=(l+r)>>1;
if(x<=mid)update(tree->lson,l,mid,x,cnt);
else update(tree->rson,mid+1,r,x,cnt);
pushup(tree,l,r);
}
int query(tree2 *tree,int l,int r,int x){
if(l==r){
cout<<tree->ml<<endl;
return tree->ml;
}
int mid=(l+r)>>1;
if(x<=mid){
if(x+tree->lson->fr>mid)return tree->lson->fr+tree->rson->fl;
else return query(tree->lson,l,mid,x);
}
else{
if(x-tree->rson->fl<=mid)return tree->lson->fr+tree->rson->fl;
else return query(tree->rson,mid+1,r,x);
}
}
int main(){
int x;
char st[10];
stack<int>s;
while(cin>>n>>m){
memset(dizhi,0,sizeof(dizhi));
build(root,1,n);
FOR(i,1,m){
scanf("%s",st);
switch(st[0]){
case 'D':
scanf("%d",&x);
s.push(x);
update(root,1,n,x,0);
break;
case 'Q':
scanf("%d",&x);
printf("%d\n",query(root,1,n,x));
break;
case 'R':
if(!s.empty()){
x=s.top();s.pop();
update(root,1,n,x,1);
}
break;
}
}
s=stack<int>();
}
return 0;
}
数据结构C题
根据上题得到的经验,用LL,LR,RL,RR来维护连通块个数。
cnt=lcnt+rcnt,如果LR、RL都存在,那cnt--;
数据范围较大,需要离散化。离散化的基础上每个点都相隔一个点(因为上述维护操作维护的是连续1的有多少个块,而此题
不把1~3、3~5这种当做一个块,所以隔开避免这种情况)
#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
using namespace std;
#define M 500050
int n,m,t=1;
int L[M],R[M];
int A[M<<1],cnt=0;
struct tree2{
tree2 *lson,*rson;
int num,fl,fr;
int lazy=-1;
}dizhi[M<<1],*root=&dizhi[0];
void push_up(tree2 *tree,int l,int r){
tree->num=tree->lson->num+tree->rson->num;
if(tree->lson->fr&&tree->rson->fl)tree->num--;
tree->fl=tree->lson->fl;
tree->fr=tree->rson->fr;
int mid=(l+r)>>1;
if(tree->fl==mid-l+1)tree->fl+=tree->rson->fl;
if(tree->fr==r-mid)tree->fr+=tree->lson->fr;
}
void push_down(tree2 *tree,int l,int r){
if(tree->lazy==-1||tree->lson==NULL)return ;
int mid=(l+r)>>1;
tree->lson->num=1,tree->lson->fl=tree->lson->fr=mid-l+1;
tree->rson->num=1,tree->rson->fl=tree->rson->fr=r-mid;
tree->lson->lazy=tree->rson->lazy=1;
tree->lazy=-1;
}
void build(tree2 *tree,int l,int r){
if(l==r){
tree->num=tree->fl=tree->fr=0;
return ;
}
int mid=(l+r)>>1;
tree->lson=&dizhi[t++];
tree->rson=&dizhi[t++];
build(tree->lson,l,mid);
build(tree->rson,mid+1,r);
push_up(tree,l,r);
}
void update(tree2 *tree,int l,int r,int x,int y){
if(x<=l&&y>=r){
tree->num=1;
tree->fl=tree->fr=r-l+1;
tree->lazy=1;
return ;
}
push_down(tree,l,r);
//printf("%d%d%d\n",tree->num,tree->lson->num,tree->rson->num);
//printf("%d %d = %d %d = %d %d\n",tree->fl,tree->fr,tree->lson->fl,tree->lson->fr,tree->rson->fl,tree->rson->fr);
int mid=(l+r)>>1;
if(x<=mid)update(tree->lson,l,mid,x,y);
if(y>mid)update(tree->rson,mid+1,r,x,y);
push_up(tree,l,r);
}
int main(){
cin>>n;
build(root,1,M);
FOR(i,1,n){
scanf("%d%d",&L[i],&R[i]);
A[++cnt]=L[i],A[++cnt]=R[i];
}
sort(A+1,A+1+cnt);
int number=unique(A+1,A+1+cnt)-(A+1);
FOR(i,1,n){
L[i]=2*(lower_bound(A+1,A+1+number,L[i])-A)-1;
R[i]=2*(lower_bound(A+1,A+1+number,R[i])-A)-1;
}
FOR(i,1,n){
update(root,1,M,L[i],R[i]);
printf("%d\n",root->num);
}
}
区间覆盖的染色问题【类似题目】
对于覆盖染色,这也是区间合并的一道好题。
但是对于多个区间询问染色:颜色不能太多,或者是最后询问一次:颜色可以较多。
维护一个关系,每个区间的颜色,如果父区间下有多个颜色,则赋值为-1.
询问的时候遇到-1就往下继续搜,类似于之前对区间和的询问(指定区间继续往下,这样减少了遍历的复杂度)
询问复杂度顶多是颜色*log。这里只给模板啦,很好写的o(* ̄▽ ̄*)ブ要自己写哦
bool vis[35];
struct tree2{
tree2 *lson,*rson;
int col,lazy;
}dizhi[M<<1],*root=&dizhi[0];
void push_up(tree2 *tree,int l,int r){
if(tree->lson->col==tree->rson->col){
tree->col=tree->lson->col;
}
else{
tree->col=-1;
}
}
void push_down(tree2 *tree,int l,int r){
if(!tree->lazy||tree->lson==NULL)return ;
tree->lson->col=tree->lazy;
tree->rson->col=tree->lazy;
tree->lson->lazy=tree->lazy;
tree->rson->lazy=tree->lazy;
tree->lazy=0;
}
void build(tree2 *tree,int l,int r){
if(l==r){
tree->col=0;
return ;
}
tree->lson=&dizhi[t++];
tree->rson=&dizhi[t++];
int mid=(l+r)>>1;
build(tree->lson,l,mid);
build(tree->rson,mid+1,r);
push_up(tree,l,r);
}
void update(tree2 *tree,int l,int r,int x,int y,int d){
if(x<=l&&r<=y){
tree->col=d;
tree->lazy=d;
return ;
}
push_down(tree,l,r);
int mid=(l+r)>>1;
if(x<=mid)update(tree->lson,l,mid,x,y,d);
if(y>mid)update(tree->rson,mid+1,r,x,y,d);
push_up(tree,l,r);
}
void query(tree2 *tree,int l,int r,int x,int y){
int mid=(l+r)>>1;
if(x<=l&&r<=y){
if(tree->col==-1){
push_down(tree,l,r);
query(tree->lson,l,mid,x,y);
query(tree->rson,mid+1,r,x,y);
}
else{
if(vis[tree->col]==false)
cnt++,vis[tree->col]=true;
}
return ;
}
push_down(tree,l,r);
if(x<=mid)query(tree->lson,l,mid,x,y);
if(y>mid)query(tree->rson,mid+1,r,x,y);
}
两道题中有一道需要离散化。
对于数据结构C题和这道题的离散化,具体要求是不同。
前者要保证相邻区间隔开,所以直接每个点都插一个点即可。
后者要保证相邻区间不隔开,且每个区间中一定要有中间的长度。|____|__|___|,假设一共覆盖了大区间,左边区间,右边区间。普通离散化会导致区间没有长度,对于这种情况,中间的区间会没有,导致答案错误。
void Lisan(){
for(int i=1; i<=m; i++) {
scanf("%d%d",&L[i],&R[i]);
lsh[2*i-1]=L[i];lsh[2*i]=R[i];
lsh[i+2*m]=L[i]+1;
}
sort(lsh+1 , lsh+3*m+1);
number = unique(lsh+1 , lsh+3*m+1) - lsh - 1;//unique返回的是:最后的位置往后一位。
for(int i=1; i<=m; i++)
{
L[i] =(lower_bound(lsh+1 , lsh+number+1 , L[i]) - lsh);
R[i] =(lower_bound(lsh+1, lsh+number+1 , R[i]) - lsh);
}
}