线段树套线段树

二维线段树一般用线段树套线段树写,当然也可以用四叉树

树套树,顾名思义,外层树的每个节点都是一棵树。

题目地址:https://www.luogu.org/problemnew/show/U22582

首先要了解的知识:

  1. 线段树
  2. 标记永久化

实现方法:

一般是先按x轴建外层树,然后在按y轴建树。

内层树更普通线段树一样,外层树每次都更新节点。

为了方便,一般树套树用标记永久化来写。

而在区间加&&区间求和中,就必须用标记永久化。

代码请在最后的完整代码中找。

图解:



第二张倒过来看233

建树

内层线段树没有区别。

按照第二张图解,外层线段树对应每个节点等于它的左右儿子的对应节点的和。

比如节点1等于两棵子树对应的节点1的和。

更新

不管是区间更新和点更新,标记永久化都比较方便。

内层和原来一样。

外层的更新就是直接更新节点对应的内层线段树。

下图中绿色的矩形区域就是目标区域,就是外层树中每个绿色节点的内层树的每个红色节点所代表的区域。

查询

上图中绿色的矩形区域就是要求的区域,相当于外层树中每个绿色节点的内层树的每个红色节点所代表的区域的和。

代码:

#include<cstdio>
#define gc getchar
int gi(){int x=0,f=0;char c=gc();while(c<'0'||'9'<c){if(c=='-')f=!f;c=gc();}while('0'<=c&&c<='9'){x=x*10+c-48;c=gc();}return f?(-x):x;}
using namespace std;
#define N 2010
int D,S,q;
struct xds{//内层(标记永久化)
    #define Z int m=(l+r)>>1
    #define ls rt<<1
    #define rs rt<<1|1
    int s[N*4],tag[N*4];
    void build(int l,int r,int rt){//内层建树
        if(l==r){s[rt]=gi();return;}
        Z;build(l,m,ls);build(m+1,r,rs);
        s[rt]=s[ls]+s[rs];
    }
    void upd(int L,int R,int v,int l,int r,int rt){//内层修改
        s[rt]+=v*(R-L+1);
        if(L==l&&r==R){
            tag[rt]+=v;
            return;
        }
        Z;
        if(R<=m)upd(L,R,v,l,m,ls);
        else{
            if(L>m)upd(L,R,v,m+1,r,rs);
            else upd(L,m,v,l,m,ls),upd(m+1,R,v,m+1,r,rs);
        }
    }
    int qh(int L,int R,int l,int r,int rt,int ad){
        if(L==l&&r==R)return s[rt]+ad*(r-l+1);
        Z;ad+=tag[rt];
        if(R<=m)return qh(L,R,l,m,ls,ad);
        else{
            if(L>m)return qh(L,R,m+1,r,rs,ad);
            else return qh(L,m,l,m,ls,ad)+qh(m+1,R,m+1,r,rs,ad);
        }
    }
}s[N*4],tag[N*4];
void mg(xds& o,xds& lc,xds& rc,int l,int r,int rt){//外层节点更新(pushup)
    o.s[rt]=lc.s[rt]+rc.s[rt];
    if(l==r)return;
    Z;mg(o,lc,rc,l,m,ls);mg(o,lc,rc,m+1,r,rs);
}
void build(int l,int r,int rt){//外层建树
    if(l==r){
        s[rt].build(1,S,1);
        return;
    }
    Z;build(l,m,ls);build(m+1,r,rs);
    mg(s[rt],s[ls],s[rs],1,S,1);
}
void upd(int x,int y,int xx,int yy,int v,int l,int r,int rt){//外层修改
    s[rt].upd(y,yy,v*(xx-x+1),1,S,1);
    if(x==l&&r==xx){
        tag[rt].upd(y,yy,v,1,S,1);
        return;
    }
    Z;
    if(xx<=m)upd(x,y,xx,yy,v,l,m,ls);
    else{
        if(x>m)upd(x,y,xx,yy,v,m+1,r,rs);
        else upd(x,y,m,yy,v,l,m,ls),upd(m+1,y,xx,yy,v,m+1,r,rs);
    }
}
int qh(int x,int y,int xx,int yy,int l,int r,int rt,int ad){//查询(求和)
    if(x==l&&r==xx)return s[rt].qh(y,yy,1,S,1,0)+ad*(r-l+1);
    Z;ad+=tag[rt].qh(y,yy,1,S,1,0);
    if(xx<=m)return qh(x,y,xx,yy,l,m,ls,ad);
    else{
        if(x>m)return qh(x,y,xx,yy,m+1,r,rs,ad);
        else return qh(x,y,m,yy,l,m,ls,ad)+qh(m+1,y,xx,yy,m+1,r,rs,ad);
    }    
}
int main(){
    D=gi();S=gi();q=gi();
    build(1,D,1);
    int p,x,y,xx,yy;
    while(q--){
        p=gi();x=gi();y=gi();xx=gi();yy=gi();
        if(p==1)printf("%d\n",qh(x,y,xx,yy,1,D,1,0));
        else upd(x,y,xx,yy,gi(),1,D,1);
    }
}

练习(感觉都比例题容易):

1. UVA11297 Census

题意:

给一个n*n的矩阵

操作1:修改点(x,y)的值为v

操作2:查询区域(x1,y1,x2,y2)中的最大值

题解:

就是把求和的更新操作改为求最大值的更新操作。

另附一种方便建树时更新的做法(直接用二维数组来存外层和内层线段树):

#include<cstdio>
#define inf 0x3f3f3f3f
int MAX(int x,int y){return x>y?x:y;}
int MIN(int x,int y){return x<y?x:y;}
#define gc getchar
int gi(){int x=0,f=0;char c=gc();while(c<'0'||'9'<c){if(c=='-')f=!f;c=gc();}while('0'<=c&&c<='9'){x=x*10+c-48;c=gc();}return f?(-x):x;}
using namespace std;
#define N 501
#define Z int m=(l+r)>>1
int a[N<<2][N<<2],b[N<<2][N<<2],n,q,ma,mi;
void pux(int xt,int yt){
    a[xt][yt]=MAX(a[xt<<1][yt],a[xt<<1|1][yt]);
    b[xt][yt]=MIN(b[xt<<1][yt],b[xt<<1|1][yt]);
}
void puy(int xt,int yt){
    a[xt][yt]=MAX(a[xt][yt<<1],a[xt][yt<<1|1]);
    b[xt][yt]=MIN(b[xt][yt<<1],b[xt][yt<<1|1]);
}
void by(int l,int r,int xt,int yt,int ff){
    if(l==r){
        if(ff)a[xt][yt]=b[xt][yt]=gi();
        else pux(xt,yt);
        return;
    }
    Z;
    by(l,m,xt,yt<<1,ff);
    by(m+1,r,xt,yt<<1|1,ff);
    puy(xt,yt);
}
void bx(int l,int r,int xt){
    if(l==r){
        by(1,n,xt,1,1);
        return;
    }
    Z;
    bx(l,m,xt<<1);
    bx(m+1,r,xt<<1|1);
    by(1,n,xt,1,0);
}
void updy(int L,int v,int l,int r,int xt,int yt,int ff){
    if(l==r){
        if(ff)a[xt][yt]=b[xt][yt]=v;
        else pux(xt,yt);
        return;
    }
    Z;
    if(L<=m)updy(L,v,l,m,xt,yt<<1,ff);
    else updy(L,v,m+1,r,xt,yt<<1|1,ff);
    puy(xt,yt);
}
void updx(int x,int y,int v,int l,int r,int xt){
    if(l==r){
        updy(y,v,1,n,xt,1,1);
        return;
    }
    Z;
    if(x<=m)updx(x,y,v,l,m,xt<<1);
    else updx(x,y,v,m+1,r,xt<<1|1);
    updy(y,v,1,n,xt,1,0);
}
void qhy(int L,int R,int l,int r,int xt,int yt){
    if(L<=l&&r<=R){
        mi=MIN(mi,b[xt][yt]);
        ma=MAX(ma,a[xt][yt]);
        return;
    }
    Z;
    if(L<=m)qhy(L,R,l,m,xt,yt<<1);
    if(R>m)qhy(L,R,m+1,r,xt,yt<<1|1);
}
void qhx(int x,int y,int xx,int yy,int l,int r,int xt){
    if(x<=l&&r<=xx){
        qhy(y,yy,1,n,xt,1);
        return;
    }
    Z;
    if(x<=m)qhx(x,y,xx,yy,l,m,xt<<1);
    if(xx>m)qhx(x,y,xx,yy,m+1,r,xt<<1|1);
}
int main(){
    n=gi();
    bx(1,n,1);
    q=gi();
    char p[2];
    int x,y,xx,yy;
    while(q--){
        scanf("%s",p);x=gi();y=gi();
        if(p[0]=='q')xx=gi(),yy=gi(),ma=-inf,mi=inf,qhx(x,y,xx,yy,1,n,1),printf("%d %d\n",ma,mi);
        else updx(x,y,gi(),1,n,1);
    }
}

2.P3437 [POI2006]TET-Tetris 3D

题意:

给定一个矩阵,初始每个位置上的元素都是0,每次选择一个子矩形,将这个子矩形内的值修改为这个子矩形内的最大值+h,求最终所有位置上的最大值

题解: 

最大值的标记永久化比求和的容易的多。

而且不用建树。

#include<cstdio>
#define inf 0x3f3f3f3f
int MAX(int x,int y){return x>y?x:y;}
#define gc getchar
int gi(){int x=0,f=0;char c=gc();while(c<'0'||'9'<c){if(c=='-')f=!f;c=gc();}while('0'<=c&&c<='9'){x=x*10+c-48;c=gc();}return f?(-x):x;}
using namespace std;
#define N 1010
int D,S,q;
struct segy{
    #define Z int m=(l+r)>>1
    #define ls rt<<1
    #define rs rt<<1|1
    int mx[N*3],tag[N*3];
    void upd(int L,int R,int v,int l,int r,int rt){
        mx[rt]=MAX(mx[rt],v);
        if(L<=l&&r<=R){
            tag[rt]=MAX(tag[rt],v);
            return;
        }
        Z;
        if(L<=m)upd(L,R,v,l,m,ls);
        if(R>m)upd(L,R,v,m+1,r,rs);
    }
    int qm(int L,int R,int l,int r,int rt){
        if(L<=l&&r<=R)return MAX(mx[rt],tag[rt]);
        Z,ans=tag[rt];
        if(L<=m)ans=MAX(ans,qm(L,R,l,m,ls));
        if(R>m)ans=MAX(ans,qm(L,R,m+1,r,rs));
        return ans;
    }
}tag[N*3],mx[N*3];
void upd(int x,int y,int xx,int yy,int v,int l,int r,int rt){
    mx[rt].upd(y,yy,v,1,S,1);
    if(x<=l&&r<=xx){
        tag[rt].upd(y,yy,v,1,S,1);
        return;
    }
    Z;
    if(x<=m)upd(x,y,xx,yy,v,l,m,ls);
    if(xx>m)upd(x,y,xx,yy,v,m+1,r,rs);
}
int qm(int x,int y,int xx,int yy,int l,int r,int rt){
    if(x<=l&&r<=xx)return MAX(tag[rt].qm(y,yy,1,S,1),mx[rt].qm(y,yy,1,S,1));
    Z,ans=tag[rt].qm(y,yy,1,S,1);
    if(x<=m)ans=MAX(ans,qm(x,y,xx,yy,l,m,ls));
    if(xx>m)ans=MAX(ans,qm(x,y,xx,yy,m+1,r,rs));
    return ans;
}
int main(){
    D=gi()+1;S=gi()+1;q=gi();
    int c,k,v,x,y;
    while(q--){
        c=gi();k=gi();v=gi();x=gi()+1;y=gi()+1;
        upd(x,y,x+c-1,y+k-1,qm(x,y,x+c-1,y+k-1,1,D,1)+v,1,D,1);
    }
    printf("%d\n",qm(1,1,D,S,1,D,1));
}

3.HDU 4819 Mosaic

题意:

给定一个n*n的矩阵,每次给定一个子矩阵区域(x,y,l),求出该区域内的最大值(A)和最小值(B),输出(A+B)/2,并用这个值更新矩阵[x,y]的值

题解:

和例题1没什么太大区别

#include<cstdio>
#define inf 0x3f3f3f3f
int MAX(int x,int y){return x>y?x:y;}
int MIN(int x,int y){return x<y?x:y;}
#define gc getchar
int gi(){int x=0,f=0;char c=gc();while(c<'0'||'9'<c){if(c=='-')f=!f;c=gc();}while('0'<=c&&c<='9'){x=x*10+c-48;c=gc();}return f?(-x):x;}
using namespace std;
#define N 1034
#define Z int m=(l+r)>>1
int a[N<<2][N<<2],b[N<<2][N<<2],n,q,ma,mi;
void pux(int xt,int yt){
    a[xt][yt]=MAX(a[xt<<1][yt],a[xt<<1|1][yt]);
    b[xt][yt]=MIN(b[xt<<1][yt],b[xt<<1|1][yt]);
}
void puy(int xt,int yt){
    a[xt][yt]=MAX(a[xt][yt<<1],a[xt][yt<<1|1]);
    b[xt][yt]=MIN(b[xt][yt<<1],b[xt][yt<<1|1]);
}
void by(int l,int r,int xt,int yt,int ff){
    if(l==r){
        if(ff)a[xt][yt]=b[xt][yt]=gi();
        else pux(xt,yt);
        return;
    }
    Z;
    by(l,m,xt,yt<<1,ff);
    by(m+1,r,xt,yt<<1|1,ff);
    puy(xt,yt);
}
void bx(int l,int r,int xt){
    if(l==r){
        by(1,n,xt,1,1);
        return;
    }
    Z;
    bx(l,m,xt<<1);
    bx(m+1,r,xt<<1|1);
    by(1,n,xt,1,0);
}
void updy(int L,int v,int l,int r,int xt,int yt,int ff){
    if(l==r){
        if(ff)a[xt][yt]=b[xt][yt]=v;
        else pux(xt,yt);
        return;
    }
    Z;
    if(L<=m)updy(L,v,l,m,xt,yt<<1,ff);
    else updy(L,v,m+1,r,xt,yt<<1|1,ff);
    puy(xt,yt);
}
void updx(int x,int y,int v,int l,int r,int xt){
    if(l==r){
        updy(y,v,1,n,xt,1,1);
        return;
    }
    Z;
    if(x<=m)updx(x,y,v,l,m,xt<<1);
    else updx(x,y,v,m+1,r,xt<<1|1);
    updy(y,v,1,n,xt,1,0);
}
void qhy(int L,int R,int l,int r,int xt,int yt){
    if(L<=l&&r<=R){
        mi=MIN(mi,b[xt][yt]);
        ma=MAX(ma,a[xt][yt]);
        return;
    }
    Z;
    if(L<=m)qhy(L,R,l,m,xt,yt<<1);
    if(R>m)qhy(L,R,m+1,r,xt,yt<<1|1);
}
void qhx(int x,int y,int xx,int yy,int l,int r,int xt){
    if(x<=l&&r<=xx){
        qhy(y,yy,1,n,xt,1);
        return;
    }
    Z;
    if(x<=m)qhx(x,y,xx,yy,l,m,xt<<1);
    if(xx>m)qhx(x,y,xx,yy,m+1,r,xt<<1|1);
}
int main(){
    int T=gi(),Case=0,X,Y,l,x,y,xx,yy,ans;
    while(T--){
        n=gi();
        bx(1,n,1);
        q=gi();
        printf("Case #%d:\n",++Case);
        while(q--){
            ma=-inf;mi=inf;
            X=gi();Y=gi();l=gi()/2;
            x=MAX(1,X-l);y=MAX(1,Y-l);
            xx=MIN(X+l,n);yy=MIN(Y+l,n);
            qhx(x,y,xx,yy,1,n,1);
            ans=(ma+mi)>>1;
            printf("%d\n",ans);
            updx(X,Y,ans,1,n,1);
        }
    }
}

4.Poj 2155 Matrix

题意:

一个n*n的矩阵一开始全是0

操作C:将区域(x1,y1,x2,y2)中的数翻转(0的变1,1的变0)

操作Q:查询位置(x,y)的数是多少

题解:

区间修改,单点查询

标记永久化的优势充分地显示出来。

#include<cstdio>
#include<cstring>
#define clr(x,y) memset(x,y,sizeof(x))
#define gc getchar
#define pc putchar
int gi(){int x=0,f=0;char c=gc();while(c<'0'||'9'<c){if(c=='-')f=!f;c=gc();}while('0'<=c&&c<='9'){x=x*10+c-48;c=gc();}return f?(-x):x;}
using namespace std;
#define N 1010
int n,q;
struct segy{
    #define Z int m=(l+r)>>1
    #define ls rt<<1
    #define rs rt<<1|1
    bool tag[N*3];
    void clean(){clr(tag,0);}
    void upd(int L,int R,int l,int r,int rt){
        if(L<=l&&r<=R){tag[rt]^=1;return;}
        Z;
        if(L<=m)upd(L,R,l,m,ls);
        if(R>m)upd(L,R,m+1,r,rs);
    }
    bool ask(int L,int l,int r,int rt,bool ad){
        ad^=tag[rt];
        if(l==r)return ad;
        Z;
        if(L<=m)return ask(L,l,m,ls,ad);
        else return ask(L,m+1,r,rs,ad);
    }
}tag[N*3];
void upd(int x,int y,int xx,int yy,int l,int r,int rt){
    if(x<=l&&r<=xx){tag[rt].upd(y,yy,1,n,1);return;}
    Z;
    if(x<=m)upd(x,y,xx,yy,l,m,ls);
    if(xx>m)upd(x,y,xx,yy,m+1,r,rs);
}
bool ask(int x,int y,int l,int r,int rt,int ad){
    ad^=tag[rt].ask(y,1,n,1,0);
    if(l==r)return ad;
    Z;
    if(x<=m)return ask(x,y,l,m,ls,ad);
    else return ask(x,y,m+1,r,rs,ad);
}
void clean(int l,int r,int rt){
    tag[rt].clean();
    if(l==r)return;
    Z;clean(l,m,ls);clean(m+1,r,rs);
}
int main(){
int T=gi();
while(T--){
    n=gi();q=gi();
    clean(1,n,1);
    char p[2];int x,y,xx,yy;
    while(q--){
        scanf("%s",p);x=gi();y=gi();
        if(p[0]=='Q')pc(ask(x,y,1,n,1,0)+48),pc('\n');
        else xx=gi(),yy=gi(),upd(x,y,xx,yy,1,n,1);
    }
    pc('\n');
}
}

5.Vijos 1512 SuperBrother打鼹鼠

题意:

矩阵大小为n*n

操作1:点(x,y)新出现k只鼹鼠

操作2:查询区域(x1,y1,x2,y2)中鼹鼠的总数

题解:

单点修改,区间求和。

试一试用四叉树写

#include<cstdio>
#define gc getchar
int gi(){int x=0,f=0;char c=gc();while(c<'0'||'9'<c){if(c=='-')f=!f;c=gc();}while('0'<=c&&c<='9'){x=x*10+c-48;c=gc();}return f?(-x):x;}
using namespace std;
#define N 20971520
#define c1 (rt*4-2)
#define c2 (rt*4-1)
#define c3 (rt*4)
#define c4 (rt*4+1)
#define Z int mx=(x+xx)>>1,my=(y+yy)>>1
int s[N],n;
bool b[N];
#define ca x,y,mx,my
#define cb x,my+1,mx,yy
#define cc mx+1,y,xx,my
#define cd mx+1,my+1,xx,yy
#define pu s[rt]=s[c1]+s[c2]+s[c3]+s[c4]
void build(int rt,int x,int y,int xx,int yy){
    if(x>xx||y>yy){b[rt]=1;return;}
    if(x==xx&&y==yy)return;
    int mx=(x+xx)>>1,my=(y+yy)>>1;
    build(c1,ca);build(c2,cb);
    build(c3,cc);build(c4,cd);
}
void upd(int X,int Y,int v,int rt,int x,int y,int xx,int yy){
    if(b[rt])return;
    if(x==xx&&y==yy){s[rt]+=v;return;}
    Z;
    if(X<=mx&&Y<=my)upd(X,Y,v,c1,ca);
    if(X<=mx&&Y>my)upd(X,Y,v,c2,cb);
    if(X>mx&&Y<=my)upd(X,Y,v,c3,cc);
    if(X>mx&&Y>my)upd(X,Y,v,c4,cd);
    pu;
}
int qh(int X,int Y,int XX,int YY,int rt,int x,int y,int xx,int yy){
    if(b[rt])return 0;
    if(X<=x&&xx<=XX&&Y<=y&&yy<=YY)return s[rt];
    Z,ans=0;
    if(X<=mx&&Y<=my)ans+=qh(X,Y,XX,YY,c1,ca);
    if(X<=mx&&YY>my)ans+=qh(X,Y,XX,YY,c2,cb);
    if(XX>mx&&Y<=my)ans+=qh(X,Y,XX,YY,c3,cc);
    if(XX>mx&&YY>my)ans+=qh(X,Y,XX,YY,c4,cd);
    return ans;
}
int main(){
    n=gi();
    build(1,1,1,n,n);
    int p,x,y,xx,yy;
    while(1){
        p=gi();if(p==3)break;
        x=gi()+1;y=gi()+1;
        if(p==1)upd(x,y,gi(),1,1,1,n,n);
        if(p==2)xx=gi()+1,yy=gi()+1,printf("%d\n",qh(x,y,xx,yy,1,1,1,n,n));
    }
}

猜你喜欢

转载自www.cnblogs.com/mimiorz/p/10295452.html