线段树

来源:互联网 发布:php用户权限管理思路 编辑:程序博客网 时间:2024/06/05 23:03

线段树,是可以进行如下操作的树状数据结构。
1.对区间[l,r]中所有的数字+x/修改为x;
2.查询区间[l,r]的总和/最大值/最小值……
线段树的具体思想可以参考百度百科。

以下是几道线段树的例题。
codevs 1082(线段树练习3)
线段树裸题,直接套用模板即可,代码如下。

#include<iostream>#include<stdio.h>#include<queue>using namespace std;struct node{    int l,r;    long long sum,lazy;} tree[800050];int n;void push_down(int id){    tree[id*2].sum+=(tree[id*2].r-tree[id*2].l+1)*tree[id].lazy;    tree[id*2].lazy+=tree[id].lazy;    tree[id*2+1].sum+=(tree[id*2+1].r-tree[id*2+1].l+1)*tree[id].lazy;    tree[id*2+1].lazy+=tree[id].lazy;    tree[id].lazy=0;}void build_tree(int id,int l,int r){    tree[id].l=l,tree[id].r=r;    tree[id].lazy=0;    if(l==r)    {    scanf("%lld",&tree[id].sum);    return;    }    int mid=(l+r)/2;    build_tree(id*2,l,mid);    build_tree(id*2+1,mid+1,r);    tree[id].sum=tree[2*id].sum+tree[2*id+1].sum;}void insert(int id,int l,int r,long long num){    int tl=tree[id].l,tr=tree[id].r;    if(tl>=l&&tr<=r)    {     //   cout<<id<<"we"<<endl;        tree[id].sum+=num*(tr-tl+1);        tree[id].lazy+=num;        return;    }    push_down(id);    int mid=(tl+tr)/2;    if(mid>=l)        insert(id*2,l,r,num);    if(mid<r)        insert(id*2+1,l,r,num);    tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;}long long ret=0;void query(int id,int l,int r){    int tl=tree[id].l,tr=tree[id].r;    if(tl>=l&&tr<=r)    {       // cout<<id<<endl;        ret+=tree[id].sum;        return;    }    push_down(id);    int mid=(tl+tr)/2;    if(mid>=l)        query(id*2,l,r);    if(mid<r)        query(id*2+1,l,r);    tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;}int main(){    int n;    cin>>n;    build_tree(1,1,n);    int q;    cin>>q;    while(q--)    {        int a;        scanf("%d",&a);        if(a==1)        {            int x,y,z;            scanf("%d%d%d",&x,&y,&z);            insert(1,x,y,(long long)z);        }        else        {            int x,y;        scanf("%d%d",&x,&y);            ret=0;            query(1,x,y);            cout<<ret<<endl;        }    }}

HDU 1698

简单的以dota中屠夫为背景的题目。题意如下,给定1~n的初始均为1的数列,每次将l~r区间里的数字全部修改为一个给定值,求最终的区间总和。
利用线段树可以简单的求解,注意在区间修改时lazy标记的-1的问题。

#include<iostream>#include<stdio.h>#include<queue>using namespace std;struct node{    int l,r;    long long sum,lazy;} tree[800050];int n;void push_down(int id){    if(tree[id].lazy!=-1)    {    tree[id*2].sum=(tree[id*2].r-tree[id*2].l+1)*tree[id].lazy;    tree[id*2].lazy=tree[id].lazy;    tree[id*2+1].sum=(tree[id*2+1].r-tree[id*2+1].l+1)*tree[id].lazy;    tree[id*2+1].lazy=tree[id].lazy;    }    tree[id].lazy=-1;}void build_tree(int id,int l,int r){    tree[id].l=l,tree[id].r=r;    tree[id].lazy=0;    if(l==r)    {    tree[id].sum=1;    return;    }    int mid=(l+r)/2;    build_tree(id*2,l,mid);    build_tree(id*2+1,mid+1,r);    tree[id].sum=tree[2*id].sum+tree[2*id+1].sum;}void insert(int id,int l,int r,long long num){    int tl=tree[id].l,tr=tree[id].r;    if(tl>=l&&tr<=r)    {        tree[id].sum=num*(tr-tl+1);        tree[id].lazy=num;        return;    }    push_down(id);    int mid=(tl+tr)/2;    if(mid>=l)        insert(id*2,l,r,num);    if(mid<r)        insert(id*2+1,l,r,num);    tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;}long long ret=0;void query(int id,int l,int r){    int tl=tree[id].l,tr=tree[id].r;    if(tl>=l&&tr<=r)    {       // cout<<id<<endl;        ret+=tree[id].sum;        return;    }    push_down(id);    int mid=(tl+tr)/2;    if(mid>=l)        query(id*2,l,r);    if(mid<r)        query(id*2+1,l,r);    tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;}int T;int main(){    cin>>T;    for(int t=1;t<=T;t++)    {    int n;    cin>>n;    build_tree(1,1,n);    insert(1,1,n,1);    int q;    cin>>q;    while(q--)    {        int a,b,c;        scanf("%d%d%d",&a,&b,&c);        insert(1,a,b,c);    }    ret=0;    query(1,1,n);    printf("Case %d: The total value of the hook is %I64d.\n",t,ret);    }}

bzoj1012
具体题意请阅读链接中的题意,这道题对于审题能力有很高的要求。
简单题,运用线段树十分容易理解,也可以使用单调队列或单调栈求解,以下给出使用线段树求解的代码。

#include<iostream>#include<stdio.h>#include<queue>using namespace std;struct node{    int l,r;    long long maxn,lazy;} tree[800050];int n;void push_down(int id){    if(tree[id].lazy!=-1)    {    tree[id*2].maxn=tree[id].lazy;    tree[id*2].lazy=tree[id].lazy;    tree[id*2+1].maxn=tree[id].lazy;    tree[id*2+1].lazy=tree[id].lazy;    }    tree[id].lazy=-1;}void build_tree(int id,int l,int r){    tree[id].l=l,tree[id].r=r;    tree[id].lazy=-1;    tree[id].maxn=0;    if(l==r)    {    return;    }    int mid=(l+r)/2;    build_tree(id*2,l,mid);    build_tree(id*2+1,mid+1,r);    tree[id].maxn=max(tree[2*id].maxn,tree[2*id+1].maxn);}void insert(int id,int l,int r,long long num){    int tl=tree[id].l,tr=tree[id].r;    if(tl>=l&&tr<=r)    {     //   cout<<id<<"we"<<endl;        tree[id].maxn=num;        tree[id].lazy=num;        return;    }    push_down(id);    int mid=(tl+tr)/2;    if(mid>=l)        insert(id*2,l,r,num);    if(mid<r)        insert(id*2+1,l,r,num);    tree[id].maxn=max(tree[2*id].maxn,tree[2*id+1].maxn);}long long ret=0;void query(int id,int l,int r){    int tl=tree[id].l,tr=tree[id].r;    if(tl>=l&&tr<=r)    {       // cout<<id<<endl;        ret=max(tree[id].maxn,ret);        return;    }    push_down(id);    int mid=(tl+tr)/2;    if(mid>=l)        query(id*2,l,r);    if(mid<r)        query(id*2+1,l,r);    tree[id].maxn=max(tree[2*id].maxn,tree[2*id+1].maxn);}int main(){    long long m,q;    cin>>q>>m;    int tot=1;    build_tree(1,1,200000);    while(q--)    {        char c[3];        long long num;        scanf("%s%lld",&c,&num);        if(c[0]=='A')        {            insert(1,tot,tot,(num+ret)%m);            tot++;        }        else        {            ret=0;            query(1,tot-num,tot-1);            printf("%lld\n",ret);        }    }}

poj 2828
一道以春运为背景的题目,一共有n次插入,每次插入时将编号为val的人插在第pos个人身后,求最终val的序列。
从后往前枚举,只需插入至从第一个开始的第pos+1个空位即可,代码如下。

#include<iostream>#include<stdio.h>#include<queue>using namespace std;struct node{    int l,r;    long long sum,lazy;} tree[800050];int n;void push_down(int id){    tree[id*2].sum+=(tree[id*2].r-tree[id*2].l+1)*tree[id].lazy;    tree[id*2].lazy+=tree[id].lazy;    tree[id*2+1].sum+=(tree[id*2+1].r-tree[id*2+1].l+1)*tree[id].lazy;    tree[id*2+1].lazy+=tree[id].lazy;    tree[id].lazy=0;}void build_tree(int id,int l,int r){    tree[id].l=l,tree[id].r=r;    tree[id].lazy=0;    if(l==r)    {    tree[id].sum=1;    return;    }    int mid=(l+r)/2;    build_tree(id*2,l,mid);    build_tree(id*2+1,mid+1,r);    tree[id].sum=tree[2*id].sum+tree[2*id+1].sum;}void insert(int id,int l,int r,long long num){    int tl=tree[id].l,tr=tree[id].r;    if(tl>=l&&tr<=r)    {     //   cout<<id<<"we"<<endl;        tree[id].sum+=num*(tr-tl+1);        tree[id].lazy+=num;        return;    }    push_down(id);    int mid=(tl+tr)/2;    if(mid>=l)        insert(id*2,l,r,num);    if(mid<r)        insert(id*2+1,l,r,num);    tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;}int ret=0;void query(int id,int l,int r){    int tl=tree[id].l,tr=tree[id].r;    if(tl>=l&&tr<=r)    {        if(tree[id].sum==0)        {            ret=min(ret,tl);            return;        }        if(tree[id].sum==tr-tl+1)            return;    }    push_down(id);    int mid=(tl+tr)/2;    if(mid>=l)        query(id*2,l,r);    if(mid<r)        query(id*2+1,l,r);    tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;}int a[200050],b[200050],ans[200050];void push(int id,int yu,int num){    int tl=tree[id].l,tr=tree[id].r;    if(tl==tr)    {        tree[id].sum--;        ans[tl]=num;        return;    }    int lson=id*2,rson=id*2+1;    if(tree[lson].sum>=yu)        push(lson,yu,num);    else        push(rson,yu-tree[lson].sum,num);    tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;}int main(){    while(cin>>n)    {            build_tree(1,1,n);        for(int i=n;i>=1;i--)        {            scanf("%d%d",&a[i],&b[i]);        }        for(int i=1;i<=n;i++)        {            a[i]++;            push(1,a[i],b[i]);        }        for(int i=1;i<=n;i++)            printf("%d%c",ans[i],i==n?'\n':' ');    }}

SGU 128
一道较难的题,具体题意请自主翻译。
解决时需要用到并查集,注意:最终所有的点必须全部联通。
代码如下:

#include<iostream>#include<stdio.h>#include<queue>#include<utility>using namespace std;struct node{    int l,r;    int sum,lazy;} tree[80005];int n;int bcj[10005];void ji(){    for(int i=1;i<=n;i++)        bcj[i]=i;}int cha(int x){    if(bcj[x]==x)        return x;    return bcj[x]=cha(bcj[x]);}void bing(int x,int y){    bcj[cha(x)]=cha(y);}priority_queue<pair<int,int> > q[20005];int q1[20005];void push_down(int id){    tree[id*2].sum+=(tree[id*2].r-tree[id*2].l+1)*tree[id].lazy;    tree[id*2].lazy+=tree[id].lazy;    tree[id*2+1].sum+=(tree[id*2+1].r-tree[id*2+1].l+1)*tree[id].lazy;    tree[id*2+1].lazy+=tree[id].lazy;    tree[id].lazy=0;}void build_tree(int id,int l,int r){    tree[id].l=l,tree[id].r=r;    tree[id].lazy=0;    if(l==r)    {    return;    }    int mid=(l+r)/2;    build_tree(id*2,l,mid);    build_tree(id*2+1,mid+1,r);    tree[id].sum=tree[2*id].sum+tree[2*id+1].sum;}void insert(int id,int l,int r,long long num){    int tl=tree[id].l,tr=tree[id].r;    if(tl>=l&&tr<=r)    {     //   cout<<id<<"we"<<endl;        tree[id].sum+=num*(tr-tl+1);        tree[id].lazy+=num;        return;    }    push_down(id);    int mid=(tl+tr)/2;    if(mid>=l)        insert(id*2,l,r,num);    if(mid<r)        insert(id*2+1,l,r,num);    tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;}int yy[20050];long long ret=0;void query(int id,int l,int r){    int tl=tree[id].l,tr=tree[id].r;    if(tl>=l&&tr<=r)    {       // cout<<id<<endl;        ret+=tree[id].sum;        return;    }    push_down(id);    int mid=(tl+tr)/2;    if(mid>=l)        query(id*2,l,r);    if(mid<r)        query(id*2+1,l,r);    tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;}int main(){    cin>>n;    ji();    int maxx=0,maxy=0;    for(int i=1;i<=n;i++)    {        int x,y;        cin>>x>>y;        x+=10001,y+=10001;        q[y].push(make_pair(x,i));        q1[x]++;        maxx=max(x,maxx);        maxy=max(y,maxy);    }    build_tree(1,1,maxx);    long long ans=0;    int tot=0;    for(int i=1;i<=maxy;i++)    {        int qs=q[i].size();        if(qs)            ++tot;        if(qs%2==1)        {            cout<<0<<endl;            return 0;        }        pair<int,int> pre;        for(int j=1;j<=qs;j++)        {            pair<int,int> e=q[i].top();            int ef=e.first,es=e.second,pref=pre.first,pres=pre.second;            q[i].pop();            if(j%2==0)            {                //cout<<es<<' '<<pres<<endl;                bing(es,pres);                ans-=ef;                ret=0;                int flag1=0,flag2=0;                query(1,ef,ef);                bing(es,pres);                if(ret)                {                    ans+=i-ret;                    bing(yy[ef],es);                    flag1=1;                    insert(1,ef,ef,-ret);                }                ret=0;                query(1,pref,pref);                if(ret)                {                    ans+=i-ret;                    bing(yy[pref],pres);                    flag2=1;                    insert(1,pref,pref,-ret);                }                ret=0;                query(1,ef,pref);                if(ret)                {                    cout<<0<<endl;                    return 0;                }                else                 {                     if(!flag1) insert(1,ef,ef,i);                     yy[ef]=es;                     if(!flag2) insert(1,pref,pref,i);                     yy[pref]=pres;                 }            }            else {ans+=ef,pre=e;}             // cout<<ans<<endl;        }    }    int tot1=0;    for(int i=1;i<=maxx;i++)    {        if(q1[i]) ++tot1;        if(q1[i]%2==1)        {            cout<<0<<endl;            return 0;        }    }    int flag=0;    for(int i=1;i<n;i++)        if(cha(i)!=cha(i+1))         flag=1;    if(flag==1)        cout<<0<<endl;    else    cout<<ans<<endl;}

鉴于并查集写的太过形象生动,请读者借鉴时注意。

对于想要继续学习线段树的相关知识,可以继续学习以下算法/数据结构:
树状数组(BIT)(作用类似于线段树,但只支持单点的修改)
zkw线段树(线段树的非递归写法)
主席树(可持久化线段树)
树链剖分(线段树在一棵树上的应用)
对于更多的线段树资料,可以参考《挑战程序设计竞赛(第二版)》(【秋叶拓哉 岩田阳一 北川宜稔 著,巫泽俊 庄俊元 李津羽 译,人民邮电出版社2013年出版)3.3节以及《算法竞赛入门经典——训练指南》(刘汝佳 著,清华大学出版社2012年出版)3.2.3和3.2.4节
另外,codeforces上也有许多线段树的好题,以下题目可供训练参考:
251D
115E
384E
343D

0 0
原创粉丝点击