线段树

本文深入浅出地介绍了线段树这一数据结构,并通过多个实例讲解了如何使用线段树来解决区间更新和查询等问题。同时,文章还提供了一些进阶学习资源。

线段树,是可以进行如下操作的树状数据结构。
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值