线段树复习

线段树复习


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

线段树概念

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

建树

假如用u来表示当前节点的编号,则用\(u \times 2\)表示左儿子,用\(u \times 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\n",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\n",QSum(1,x,y));
        else printf("%d\n",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\n",(Query(1,1,n,x,n)+Query(1,1,n,1,y)).pa);
			else printf("%d\n",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\n",z);
		}
	}return 0;
}

总结

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

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

posted @ 2016-10-21 22:05  cycleke  阅读(354)  评论(1编辑  收藏  举报