• 线段树复习


    线段树复习


    还有将近一个月就要noip了,一定要加油啊!

    线段树概念

    线段树是为了解决一些区间(单点)修改和区间(单点)查询的数据结构。它将每个区间[l,r]划分为[l,mid]和[mid+1,r],其中mid=(l+r)/2。这样我们就可以通过两个小区间的信息快速得到大区间的信息。

    建树

    假如用u来表示当前节点的编号,则用(u imes 2)表示左儿子,用(u imes 2 + 1)表示右儿子。这棵树的叶子节点表示每个单点。

    修改操作

    对于单点的修改操作,我们只用修改整棵树的一条链即好。对于区间修改,如果此区间在一个节点上,则继续递归下去,否则我们就将区间依照建树的方法分割为两个区间继续递归。当我们发现有一个节点的区间恰好被修改区间覆盖时,我们可以打一个lazy标记,表示这段区间已经被修改,而不是继续递归。当我们要查询它的儿子时则将标记下放。总体时间复杂度(O(n log n))

    查询操作

    查询操作其实与修改操作大同小异,不过要记得标记下放。复杂度同样为(O(n log n))

    模板(区间求和和区间修改)

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #define R(a) (a << 1)
    #define L(a) (a << 1|1)
    using namespace std;
    #define maxn 200002
    #define LL long long
    struct node{
    	int l,r;
    	LL sum,add;
    }N[maxn << 2];
    LL A[maxn];
    
    void PushUp(const int& u){
        N[u].sum = N[R(u)].sum + N[L(u)].sum;
    }
    
    void Build(const int& u,const int& l,const int& r){
        N[u].l = l,N[u].r = r;
        if(l == r){
            N[u].sum = A[l];
            return;
        }
        int mid = (l + r)>>1;
        Build(L(u),l,mid);
        Build(R(u),mid + 1,r);
        PushUp(u);
    }
    
    void Pushdown(const int& u)
    {
        N[R(u)].add += N[u].add;
        N[R(u)].sum += (N[R(u)].r - N[R(u)].l + 1)*N[u].add;
        N[L(u)].add += N[u].add;
        N[L(u)].sum += (N[L(u)].r - N[L(u)].l + 1)*N[u].add;
        N[u].add = 0;
    }
    
    LL Query(const int& u,const int& l,const int& r){
        if(l <= N[u].l && r >= N[u].r) return N[u].sum;
        if(N[u].add) Pushdown(u);
        int mid = (N[u].l + N[u].r) >> 1;
        if(r <= mid) return Query(L(u),l,r);
        if(l > mid) return Query(R(u),l,r);
        return Query(L(u),l,mid) + Query(R(u),mid + 1,r);
    }
    
    void Updata(const int& u,const int& l,const int& r,const LL& c){
         if(l <= N[u].l && r >= N[u].r) {
            N[u].add += c;
            N[u].sum +=(N[u].r - N[u].l + 1) * c;
            return;
         }
         N[u].sum +=(r - l + 1)*c;
         if(N[u].add) Pushdown(u);
         int mid = (N[u].l + N[u].r) >> 1;
         if(r <= mid) Updata(L(u),l,r,c);
         else if(l > mid) Updata(R(u),l,r,c);
         else {
            Updata(L(u),l,mid,c);
            Updata(R(u),mid+1,r,c);
         }
    }
    
    int main()
    {
    	int n,m;
    	scanf("%d",&n);
    	for(int i = 1;i <= n;++i)scanf("%lld",&A[i]);
    	scanf("%d",&m);
    	Build(1,1,n);
    	int a,b;
    	LL c;
    	int Q;
    	while(m--)
    	{
    		scanf("%d",&Q);
    		if(Q == 2){
                scanf("%d%d",&a,&b);
                printf("%lld
    ",Query(1,a,b));
    		}else {
                scanf("%d%d%lld",&a,&b,&c);
                Updata(1,a,b,c);
    		}
    	}
    	return 0;
    }
    

    一些例题


    一些有简单的线段树题目bzoj1018,bzoj1858,bzoj1493。

    这些题目都是通过线段树维护一些特殊信息来的出答案。

    bzoj1018: [SHOI2008]堵塞的交通traffic

    此题我们用线段树维护每段区间的四个顶点的联通情况(不通过外部节点)。

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #include <cstring>
    using namespace std;
     
    const int Maxn = 100005;
    #define ls (u<<1)
    #define rs (u<<1|1)
    #define MID(a,b) int mid = (a+b) >> 1;
    struct node {
        bool a[2], b[2], c[2];
        node() {}
    }N[Maxn << 2];
    int C, r1, r2, c1, c2;
    bool edge[Maxn][3];
     
    void build(int u,int l,int r) {
        if (l == r) {
            N[u].b[0] = N[u].b[1] = true; return;
        }
        MID(l,r);
        build(ls,l,mid); build(rs,mid+1,r);
    }
     
    node pluse(node a, node b,bool x, bool y) {
        node c;
        c.a[0] = (a.a[0])||(a.b[0]&&a.b[1]&&x&&y&&b.a[0]);
        c.a[1] = (b.a[1])||(b.b[0]&&b.b[1]&&x&&y&&a.a[1]);
        c.b[0] = (a.b[0]&&x&&b.b[0])||(a.c[0]&&y&&b.c[1]);
        c.b[1] = (a.b[1]&&y&&b.b[1])||(a.c[1]&&x&&b.c[0]);
        c.c[0] = (x&&a.b[0]&&b.c[0])||(y&&a.c[0]&&b.b[1]);
        c.c[1] = (y&&a.b[1]&&b.c[1])||(x&&a.c[1]&&b.b[0]);
        return c;
    }
     
    void update(int u,int l,int r,int val) {
        if (l == r) {
            N[u].b[0] = N[u].b[1] = true;
            N[u].a[0] = N[u].a[1] = N[u].c[0] = N[u].c[1] = edge[val][2];
            return;
        }
        MID(l,r);
        if (val <= mid) update(ls,l,mid,val);
        else update(rs,mid+1,r,val);
        N[u] = pluse(N[ls],N[rs],edge[mid][0],edge[mid][1]);
    } 
     
    node query(int u,int l,int r,int x, int y) {
        if (l >= x && r <= y) return N[u];
        MID(l,r);
        if (y <= mid) return query(ls,l,mid,x,y);
        else if (x > mid) return query(rs,mid+1,r,x,y);
        return pluse(query(ls,l,mid,x,mid),query(rs,mid+1,r,mid+1,y),edge[mid][0],edge[mid][1]);
    }
     
    int main() {
     
        scanf("%d", &C);
        build(1,1,C);
        char S[10] = {0};
        node tmp1, tmp2, tmp3; bool ans;
        while (scanf("%s",S)) { 
            if (*S == 'E') break;
            scanf("%d%d%d%d",&r1,&c1,&r2,&c2);
            --r1, --r2;
            if (c1 > c2) swap(r1,r2), swap(c1,c2);
            if (*S == 'O') {
                if (r1 > r2) swap(r1,r2), swap(c1,c2);
                if (r1 < r2) {
                    edge[c1][2] = 1; update(1,1,C,c1);
                } else {
                    edge[c1][r1] = 1; update(1,1,C,c1);
                }
            } else if (*S == 'C') {
                if (r1 > r2) swap(r1,r2), swap(c1,c2);
                if (r1 < r2) {
                    edge[c1][2] = 0; update(1,1,C,c1);
                } else {
                    edge[c1][r1] = 0; update(1,1,C,c1);
                }
            } else  {
                tmp1 = query(1,1,C,1,c1), tmp2 = query(1,1,C,c1,c2), tmp3 = query(1,1,C,c2,C);
                if (r1 && r2) {
                    ans=tmp2.b[1]||(tmp1.a[1]&&tmp2.c[0])||(tmp2.c[1]&&tmp3.a[0]);
                    ans = ans || (tmp1.a[1]&&tmp2.b[0]&&tmp3.a[0]);
                } else if (! r1 ^ r2) {
                    ans=tmp2.b[0]||(tmp1.a[1]&&tmp2.c[1])||(tmp2.c[0]&&tmp3.a[0]);
                    ans = ans || (tmp1.a[1]&&tmp2.b[1]&&tmp3.a[0]);
                } else if (r1) {
                    ans=tmp2.c[1]||(tmp1.a[1]&&tmp2.b[0])||(tmp2.b[1]&&tmp3.a[0]);
                    ans = ans || (tmp1.a[1]&&tmp2.c[0]&&tmp3.a[0]);
                } else {
                    ans=tmp2.c[0]||(tmp1.a[1]&&tmp2.b[1])||(tmp2.b[0]&&tmp3.a[0]);
                    ans = ans || (tmp1.a[1]&&tmp2.c[1]&&tmp3.a[0]);
                }
                if (ans) puts("Y"); else puts("N");
            }
        }
        return 0;
    }
     
    // a  x  b  
    // c  y  d  
    //a[] means a-c b-d
    //b[] means a-b c-d
    //c[] means a-d b-c
    

    bzoj1858: [Scoi2010]序列操作

    此题中每个节点维护区间中的连续段数和左右端点颜色即可。

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    int ina; char inc;
    inline int geti() {
        while((inc=getchar())<'0'||inc>'9'); ina=inc-'0';
    while((inc=getchar())>='0'&&inc<='9')ina=(ina<<3)+(ina<<1)+inc-'0';
        return ina;
    }
    #define N 100005
    #define cmax(a,b) ((a)<(b)?(a)=(b):1)
    struct D {
        int l,r,l0,r0,l1,r1,mx0,mx1,sum0,sum1,re,c,all;
    }C[N<<2];
    #define ls u<<1
    #define rs u<<1|1
    inline void rever(int u) {
        swap(C[u].l0,C[u].l1); swap(C[u].r0,C[u].r1);
        swap(C[u].mx0,C[u].mx1); swap(C[u].sum0,C[u].sum1);
        if(~C[u].all) C[u].all^=1;
    }
    inline void modify(int u,int co) {
        C[u].all=co; C[u].re=0;
        if(co) {
            C[u].sum1=C[u].l1=C[u].r1=C[u].mx1=C[u].r-C[u].l+1;
            C[u].sum0=C[u].l0=C[u].r0=C[u].mx0=0;
        } else {
            C[u].sum0=C[u].l0=C[u].r0=C[u].mx0=C[u].r-C[u].l+1;
            C[u].sum1=C[u].l1=C[u].r1=C[u].mx1=0;
        }
    }
    D operator + (const D&a,const D&b) {
        D t; t.l=a.l,t.r=b.r;t.re=0; t.c=-1;
        t.l0=a.l0,t.l1=a.l1;t.r0=b.r0,t.r1=b.r1;
        t.mx0=max(a.mx0,b.mx0);t.mx1=max(a.mx1,b.mx1);
        cmax(t.mx0,a.r0+b.l0),cmax(t.mx1,a.r1+b.l1);
        t.sum0=a.sum0+b.sum0,t.sum1=a.sum1+b.sum1;
        if(a.all==0) t.l0=a.mx0+b.l0; else if(a.all==1) t.l1=a.mx1+b.l1;
        if(b.all==0) t.r0=b.mx0+a.r0; else if(b.all==1) t.r1=b.mx1+a.r1;
        (a.all^b.all)?t.all=-1:t.all=a.all; return t;
    }
    inline void Down(int u) {
        if(C[u].l==C[u].r) return;
        if(~C[u].c) {
            modify(ls,C[u].c),modify(rs,C[u].c);
            C[ls].c=C[rs].c=C[u].c; C[u].c=-1;
        }
        if(C[u].re){
            C[ls].re^=1,C[rs].re^=1;
            rever(ls), rever(rs);
            C[u].re=0;
        }
    }
    void Build(int u,int l,int r) {
        C[u].l=l,C[u].r=r,C[u].c=-1;
        if(l==r) {modify(u,geti());return;}
        int mid=l+r>>1;
        Build(ls,l,mid);Build(rs,mid+1,r); C[u]=C[ls]+C[rs];
    }
    void Update(int u,int x,int y,int op) {
        int l=C[u].l,r=C[u].r; Down(u);
        if(x==l&&r==y) {
            if(op<2) modify(u,op),C[u].c=op;
            else rever(u),C[u].re=1;
            return;
        }int mid=(l+r)>>1;
        if(y<=mid) Update(ls,x,y,op);
        else if(x>mid) Update(rs,x,y,op);
        else Update(ls,x,mid,op),Update(rs,mid+1,y,op);
        C[u]=C[ls]+C[rs];
    }
    D QC(int u,int x,int y) {
        int l=C[u].l,r=C[u].r; Down(u);
        if(x==l&&r==y) return C[u];
        int mid=(l+r)>>1;
        if(y<=mid) return QC(ls,x,y);
        else if(x>mid) return QC(rs,x,y);
        return QC(ls,x,mid)+QC(rs,mid+1,y);
    }
    int QSum(int u,int x,int y) {
        int l=C[u].l,r=C[u].r; Down(u);
        if(l==x&&r==y) return C[u].sum1;
        int mid=(l+r)>>1;
        if(y<=mid) return QSum(ls,x,y);
        else if(x>mid) return QSum(rs,x,y);
        else return QSum(ls,x,mid) + QSum(rs,mid+1,y);
    }
    int main() {
    #ifndef ONLINE_JUDGE
        freopen("operator.in","r",stdin);
        freopen("operator.out","w",stdout);
    #endif
        int n=geti(),m=geti(),x,y,op;
        Build(1,0,n-1);
        while(m--) {
            op=geti(),x=geti(),y=geti();
            if(op<3)  Update(1,x,y,op);
            else if(op==3) printf("%d
    ",QSum(1,x,y));
            else printf("%d
    ",QC(1,x,y).mx1);
        }return 0;
    } 
    

    bzoj1493: [NOI2007]项链工厂

    此题同上一道题有些相似,只不过多了个旋转和翻转操作比较麻烦。
    其实一般来说,线段树是无法做区间翻转的,不过此题是整个区间翻转,于是就可以用线段树乱搞了。

    #include <cstdio>
    #include <cstring>
    int ina; char inc;
    inline int geti() {
    	for(;(inc=getchar())<'0'||inc>'9';);
    	for(ina=inc-'0';(inc=getchar())>='0'&&inc<='9';ina=(ina<<3)+(ina<<1)+inc-'0');
    	return ina;
    }
    #define ls u<<1
    #define rs u<<1|1
    #define N 500010
    struct D{
    	int lc,rc,pa;
    	D operator + (const D&a) const {
    		D ret; ret.lc=lc,ret.rc=a.rc;
    		ret.pa=pa+a.pa; if(rc==a.lc) --ret.pa;
    		return ret;
    	}
    	inline void ch(int a)  {lc=rc=a; pa=1;}
    }C[N<<2];
    int ly[N<<2],n,c,dis,q; bool d;
    inline void Down(int u){
    	if(!ly[u]) return;
    	ly[ls]=ly[rs]=ly[u];
    	C[ls].ch(ly[u]),C[rs].ch(ly[u]);
    	ly[u]=0;
    }
    void Update(int u,int l,int r,int x,int y,int op) {
    	if(x<=l&&r<=y) {
    		ly[u]=op; C[u].ch(op);
    		return;
    	}Down(u);
    	int m=l+r>>1;
        if(y<=m) Update(ls,l,m,x,y,op);
    	else if(x>m) Update(rs,m+1,r,x,y,op);
    	else {Update(ls,l,m,x,m,op);Update(rs,m+1,r,m+1,y,op);}
    	C[u]=C[ls]+C[rs];
    }
    inline void cal(int &x,int &y) {
    	if(d){
    		x=(n+2-x-dis+n)%n;
    		y=(n+2-y-dis+n)%n;
    		x^=y^=x^=y;
    	}else x=(x-dis+n)%n,y=(y-dis+n)%n;
    	(!x)?x=n:1,(!y)?y=n:1;
    }
    int find(int u,int l,int r,int p) {
    	if(l==r) return C[u].lc;
    	Down(u); int m=l+r>>1;
    	return (p<=m)?find(ls,l,m,p):find(rs,m+1,r,p);
    }
    D Query(int u,int l,int r,int x,int y){
    	if(x<=l&&r<=y) return C[u];
    	Down(u); int m=l+r>>1;
    	if(y<=m) return Query(ls,l,m,x,y);
    	if(x>m) return Query(rs,m+1,r,x,y);
    	return Query(ls,l,m,x,m)+Query(rs,m+1,r,m+1,y);
    }
    void Build(int u,int l,int r) {
    	if(l==r) {C[u].ch(geti()); return;}
    	int m=l+r>>1; Build(ls,l,m); Build(rs,m+1,r);
    	C[u]=C[ls]+C[rs];
    }
    int main() {
    #ifndef ONLINE_JUDGE
    	freopen("T.in","r",stdin);
    #endif
    	char ts[3]; int x,y,z,cx,cy;
    	n=geti(),c=geti(); Build(1,1,n);
    	for(q=geti();q;--q) {
    		scanf("%s",ts);
    		if(*ts=='R') {
    			z=geti();
    			if(d)dis=(dis+n-z)%n;
    			else dis=(dis+z)%n;
    		}
    		else if(*ts=='F') d=!d;
    		else if(*ts=='P') {
    			x=geti(),y=geti(),z=geti(); cal(x,y);
    			if(x>y) Update(1,1,n,x,n,z),Update(1,1,n,1,y,z);
    			else Update(1,1,n,x,y,z);
    		} else if(*ts=='S') {
    			x=geti(),y=geti(); cal(x,y);
    			cx=find(1,1,n,x),cy=find(1,1,n,y);
    			Update(1,1,n,x,x,cy); Update(1,1,n,y,y,cx);
    		} else if(ts[1]=='S') {
    			x=geti(),y=geti(); cal(x,y);
    			if(x>y) printf("%d
    ",(Query(1,1,n,x,n)+Query(1,1,n,1,y)).pa);
    			else printf("%d
    ",Query(1,1,n,x,y).pa);
    		} else {
    			z=Query(1,1,n,1,n).pa-(C[1].lc==C[1].rc); (z<1)?z=1:0;
    			printf("%d
    ",z);
    		}
    	}return 0;
    }
    
    

    总结

    总的来说,线段树的适用范围是对于一系列区间修改和查询,一般不支持翻转操作。而且我们可以通过子区间快速得到大区间的答案(一般为(O(1))),亦可以打标记和快速下放标记(或者采用神奇的永久化标记)。其建树复杂度一般为(O(n)),查询和修改一般为(O(log n))。(当区间合并和下放标记复杂度不是(O(1))时,需再乘上其他多项式)。

    Ps:线段树的题目一定要思路清晰,不可以有思路就开打,因为它很难debug,一般只能采用静态查错。

  • 相关阅读:
    hdu 1058
    hdu 1003
    hdu 1500
    hdu 1083 最大匹配
    hdu 1370 中国剩余定理
    hdu 1299 数论 分解素因子
    hdu 1299
    poj 1144 求割点
    hdu 1068 最大独立集合
    hdu 1054
  • 原文地址:https://www.cnblogs.com/cycleke/p/5986214.html
Copyright © 2020-2023  润新知