总结之线段树1

线段树是利用了完全二叉树的一种数据结构,在每个节点保存一条线段,可以维护和询问一组数据,主要用于解决一段数据的动态查询问题。线段树需要维护的是子叶和根之间的变化关系并且可以查询,在预处理时耗时O(n),查询,更新操作需要o(logn),空间消耗o(n)。


线段树单点更新,每次修改对应子叶节点,相应的根节点也要改变,如hduoj 1754 I Hate It ( 点击打开题目链接

题解博客:( 点击打开题解链接

线段树区间更新是难点,由于更新一段区间如果每个节点都去更新很容易超时,可以引入一个延迟变量(懒惰变量lazy),用来标记是否需要对子叶进行更改,再在下一次修改或查询时进行更新以减少操作降低时间。

区间更新题型:加上某值(add k) poj 3468 A Simple Problem with Integers ( 点击打开题目链接

题意:有一段数,有两种操作,一:某一区间所有值加上某数。二:输出某一区间所有值的和。

#include<stdio.h>
#define LL long long
int n,m;
struct tree{
    int l,r;
    LL v,m;
}t[400100];
void build(int k,int l,int r){//构建线段树
    t[k].l=l,t[k].r=r,t[k].m=0;
    if(l==r){
        scanf("%lld",&t[k].v);
        return ;
    }
    int mid=(t[k].l+t[k].r)/2;
    build(k*2,l,mid);
    build(k*2+1,mid+1,r);
    t[k].v=t[k*2].v+t[k*2+1].v;
}
void pushup(int k){//子叶节点向上更新
    t[k].v=t[k*2].v+t[k*2+1].v;
}
void pushdown(int k){//根节点标记向下更新
    if(t[k].m){
        t[k*2].m+=t[k].m;
        t[k*2].v+=t[k].m*(t[k*2].r-t[k*2].l+1);
        t[k*2+1].m+=t[k].m;
        t[k*2+1].v+=t[k].m*(t[k*2+1].r-t[k*2+1].l+1);
        t[k].m=0;
    }
}
LL query(int k,int l,int r){//查询某区间和函数
    if(t[k].l>=l&&r>=t[k].r)  return t[k].v;
    pushdown(k);
    int mid=(t[k].l+t[k].r)/2;
    LL t1=0,t2=0;
    if(mid>=l) t1=query(k*2,l,r);
    if(mid<r)  t2=query(k*2+1,l,r);
    return t1+t2;
}
void updata(int k,int l,int r,LL add){//更新线段树
    int mid=(t[k].l+t[k].r)/2;
    if(t[k].l>=l&&r>=t[k].r){//当前的线段在查询区间内,则此线段和增加(r-l+1)*add,并做上标记
        t[k].m+=add;
        t[k].v+=(t[k].r-t[k].l+1)*add;
        return ;
    }
    pushdown(k);
    if(mid>=l) updata(k*2,l,r,add);
    if(mid<r) updata(k*2+1,l,r,add);
    pushup(k);
}
int main(){
    scanf("%d%d",&n,&m);
        build(1,1,n);
        while(m--){
            char str[10];
            int l,r,x;
            scanf("%s",str);
            if(str[0]=='Q'){
                scanf("%d%d",&l,&r);
                printf("%lld\n",query(1,l,r));
            }
            if(str[0]=='C'){
                scanf("%d%d%lld",&l,&r,&x);
                updata(1,l,r,x);
            }
        }
    return 0;
}

区间更新题型:将某区间变为某数(set k) hduoj 1698 Just a Hook ( 点击打开链接

题意:有n的数开始全为1,现在改变一段区间为1,2或3,进行m此操作,最后输出总长度。

#include<stdio.h>
int n;
struct tree{
    int l,r,m,v;
}t[400010];
void pushdown(int k){//向下传递将值置为add
    if(t[k].m){
        t[k*2].m=t[k].m;
        t[k*2].v=(t[k*2].r-t[k*2].l+1)*t[k].m;
        t[k*2+1].m=t[k].m;
        t[k*2+1].v=(t[k*2+1].r-t[k*2+1].l+1)*t[k].m;
        t[k].m=0;
    }
}
void pushup(int k){
    t[k].v=t[k*2].v+t[k*2+1].v;
}
void build(int k,int l,int r){
    t[k].l=l,t[k].r=r,t[k].m=0;
    if(t[k].r==t[k].l){
        t[k].v=1;
        return ;
    }
    int mid=(l+r)/2;
    build(k*2,l,mid);
    build(k*2+1,mid+1,r);
    pushup(k);
}
void updata(int k,int l,int r,int add){
    if(t[k].l>=l&&t[k].r<=r){
        t[k].m=add;
        t[k].v=add*(t[k].r-t[k].l+1);
        return ;
    }
    pushdown(k);
    int mid=(t[k].r+t[k].l)/2;
    if(mid>=l)  updata(k*2,l,r,add);
    if(mid<r)   updata(k*2+1,l,r,add);
    pushup(k);
}
int main(){
    int m,ca,num=1;
    scanf("%d",&m);
    while(m--){
        scanf("%d%d",&n,&ca);
        build(1,1,n);
        while(ca--){
            int l,r,x;
            scanf("%d%d%d",&l,&r,&x);
            updata(1,l,r,x);
        }
        printf("Case %d: The total value of the hook is %d.\n",num++,t[1].v);//不需要询问,每次只要输出根节点的sum就行
    }
    return 0;
}

hduoj 4027 Can you answer these queries? ( 点击打开链接)
题意:给出n个数和m次操作,操作有两种,1:将某个区间的所有值全部开方 2:输出区间值之和。由于开方,只能标记开方次数并且只有对最底层的子叶进行开方操作。

#include<stdio.h>
#include<math.h>
#define LL long long
int n;
LL a[100010];
struct tree{
    int l,r;
    LL v;
}t[400100];
void build(int k,int l,int r){
    t[k].l=l;t[k].r=r;
    if(l==r){
        scanf("%lld",&t[k].v);
        return ;
    }
    t[k].v=0;
    int mid=(l+r)/2;
    build(k*2,l,mid);
    build(k*2+1,mid+1,r);
    t[k].v=t[k*2].v+t[k*2+1].v;
}
void updata(int k,int l,int r){
    if(t[k].v==t[k].r-t[k].l+1)   return ;//这一段全为1,开方操作不影响
    if(t[k].l==t[k].r){
        t[k].v=sqrt(double(t[k].v));
        return ;
    }
    int mid=(t[k].l+t[k].r)/2;
    if(l<=mid) updata(k*2,l,r);
    if(r>mid)   updata(k*2+1,l,r);
    t[k].v=t[k*2].v+t[k*2+1].v;
}
LL query(int k,int l,int r){
    if(t[k].l>=l&&t[k].r<=r)    return t[k].v;
    int mid=(t[k].l+t[k].r)/2;
    LL t1=0,t2=0;
    if(mid>=l) t1=query(k*2,l,r);
    if(mid<r)  t2=query(k*2+1,l,r);
    return t1+t2;
}
int main(){
    int ca=1;
    while(~scanf("%d",&n)){
        int m;
        build(1,1,n);
        scanf("%d",&m);
        printf("Case #%d:\n",ca++);
        while(m--){
            int x,i,j;
            scanf("%d%d%d",&x,&i,&j);
            if(i>j) {i+=j;j=i-j;i=i-j;}
            if(x==0){
                updata(1,i,j);
            }
            else{
                printf("%lld\n",query(1,i,j));
            }
        }
        printf("\n");
    }
    return 0;
}

线段树最大连续区间:hduoj 1540 Tunnel Warfare ( 点击打开链接)

题意:给出n个数和m次操作,开始n个数可以全看为1并且长度都为n,有三种操作:D:破坏此村庄。Q:输出与此村庄联通的村庄数目。R:恢复最近一次破坏的村庄。区间长度在树中存下:此节点中最大的值sum,从左边界点向右延伸的点ll,从右边界点向左延伸的点rl。

#include<stdio.h>
#include<algorithm>
using namespace std;
int n,m;
int stk[50010],top;//用数组和top模拟栈,用来存下毁灭村庄的操作顺序
struct tree{
    int l,r;
    int len,ll,rl;
}t[200010];
void build(int k,int l,int r){
    t[k].l=l,t[k].r=r;
    t[k].ll=t[k].rl=t[k].len=r-l+1;//开始时长度都为r-l+1
    if(l==r) return ;
    int mid=(l+r)/2;
    build(k*2,l,mid);
    build(k*2+1,mid+1,r);
}
void updata(int k,int l,int add){//add为1时是修复操作,否则是毁灭操作
    if(t[k].l==t[k].r){
        if(add==1)  t[k].ll=t[k].rl=t[k].len=1;
        else t[k].ll=t[k].rl=t[k].len=0;
        return ;
    }
    int mid=(t[k].r+t[k].l)/2;
    if(l<=mid)  updata(k*2,l,add);
    else updata(k*2+1,l,add);
    if(t[k*2].ll==t[k*2].r-t[k*2].l+1)  t[k].ll=t[k*2].ll+t[k*2+1].ll;
    else t[k].ll=t[k*2].ll;
    if(t[k*2+1].rl==t[k*2+1].r-t[k*2+1].l+1)    t[k].rl=t[k*2+1].rl+t[k*2].rl;//更新节点的ll,rl
    else t[k].rl=t[k*2+1].rl;
    t[k].len=max(t[k*2].len,t[k*2+1].len);//根节点最大长度为子节点最大长度取max
    t[k].len=max(max(t[k].rl,t[k].ll),t[k].len);//若是左右孩子节点中间有连接部分,取max
}
int query(int k,int l){
    if(t[k].l==t[k].r||t[k].len==0||t[k].len==t[k].r-t[k].l+1) return t[k].len;
    int mid=(t[k].l+t[k].r)/2;
    if(mid>=l){
        if(l>=t[k*2].r-t[k*2].rl+1) return t[k*2].rl+t[k*2+1].ll;//如果查询编号在左右孩子节点的连接部分上,需要返回左rl+右ll
        else return query(k*2,l);//否则只在左孩子上
    }
    else{
        if(l<=t[k*2+1].l+t[k*2+1].ll-1) return t[k*2].rl+t[k*2+1].ll;//同理
        else return query(k*2+1,l);
    }
}
int main(){
    while(~scanf("%d%d",&n,&m)){
        build(1,1,n);
        top=0;
        while(m--){
            char str[10];
            int x;
            scanf("%s",str);
            if(str[0]=='D'){
                scanf("%d",&x);
                updata(1,x,-1);
                stk[top++]=x;//在stk中存下每次操作
            }
            else if(str[0]=='Q'){
                scanf("%d",&x);
                printf("%d\n",query(1,x));
            }
            else{
                x=stk[--top];
                updata(1,x,1);
            }
        }
    }
    return 0;
}

小结:线段树的大概框架都有个模板,基本就是建立个完全二叉树,在树上查找更新。区别在于,每一棵树上需要维护的值和方式,问题不同,维护的值,数量和方式也不同,这才是需要关注的地方。(刚接触时的小结)

(待续。。。)





  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值