线段树学习笔记及模板

概括:线段树是一种数据结构,针对需要动态 修改并且获取数据中一段信息(子段和,子段最小值等)的数据来使用

大体思路:使用一颗完全二叉树(数组实现)记录数据。树的叶子节点记录每一个数据元素,非叶子节点记录段信息。

建树,查询,修改都由二叉树的性质:第i个节点的左右孩子为2*i+1,2*i+2   递归的执行



建树思路:从根向下递归找叶子->找到叶子后对叶子赋值->赋值完叶子后回溯赋段信息

查找(查询某一段的信息)思路:从根向下递归点的段信息:

                     对于和查找的段没有交集的段,剪枝掉

                     对于被要查找的段包住的段,符合要求,回溯

                     其他的段向下递归,回溯合并的段信息

点修改思路:从根向下递归找叶子,找到需要的点就修改,在回溯中更新段信息    

特别的操作1:段修改 

                       对一段元素做同一操作,势必会影响到非叶子节点。

                       对于像给一段元素加上同一值这样的操作,可以直接给代表这段的节点操作:记录到延时标志上面,并不更新其他的值,把更新操作留到查询操作中解决。

                      对于段赋值,就需要在对这一段修改前确保这一段处于改了也没问题的状态,即延时标记为0

                       由于下推操作只有O(1)的复杂度所以可以到处用

                      所以在进行修改前下推一下就行了

    #include <bits/stdc++.h>

    using namespace std;
    const int maxn=1000;

    //用于存放线段树的结构 l,r表示此区间的左右端点
    struct segn
    {
        int data,l,r,mi,su,mark_add;
    }segt[maxn];
    //建树:叶节点终止条件->递归建左右子树->由左右孩子信息得到此节点信息
    void push_dowm(int root)
    {
        if(segt[root].mark_add!=0)
        {
            int st=segt[root].l;
            int en=segt[root].r;
            segt[root].su+=segt[root].mark_add*(en-st+1);
            segt[root].mi+=segt[root].mark_add;
            segt[root*2+1].mark_add+=segt[root].mark_add;
            segt[root*2+2].mark_add+=segt[root].mark_add;
            segt[root].mark_add=0;
        }
    }
    void build(int *dat,int root,int st,int en)
    {
        //st,en表示目前区间的首尾端点序号,dat[]表示用于初始化的数据
        //记录左右端点
        segt[root].l=st;
        segt[root].r=en;
        if(st==en)//在叶子节点上加入元素数据
        {
            segt[root].data=dat[st];
            segt[root].mark_add=0;
            segt[root].mi=segt[root].data;//求区间最小值的操作
            segt[root].su=segt[root].data;//求区间和的操作
            return;
        }
        //对于非叶子节点
        int mid=(st+en)/2;//分割点
        //由二叉性,数组存树的左右节点编号分别为此节点的*2+1和*2+2
        build(dat,root*2+1,st,mid);//递归建左子树
        build(dat,root*2+2,mid+1,en);//右子树
        //由左右子树得到此节点的数据
        //求区间最小值
        segt[root].mi=min(segt[root*2+1].mi,segt[root*2+2].mi);
        //求区间和
        segt[root].su=segt[root*2+1].su+segt[root*2+2].su;
    }

    /*
    //查询的模板
    int query_(int qst,int qen,int tst,int ten,int root)
    {//查询子段和
        push_dowm(root);
        if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝
            return 一个对合并值没有效果的值;
        if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合
            return segt[root].数据;
        int mid =(tst+ten)/2;
        return 子段合并操作//搜索左右子树
    }
   */
    const int bigmun=1e7;
    //下推操作在查询中完成
    int query_mi(int qst,int qen,int tst,int ten,int root)//查询操作:向下递归的找区间,只要找到被要找的区间包括的区间,就可以肯定这个区间是需要的(因为向下递归的顺序和不交叉的顺序)
    {//查询最小值
        //qst,qen:想查询的区间  tst,ten:正在查的区间
        push_dowm(root);
        if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝
            return bigmun;
        if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合
            return segt[root].mi;
        int mid =(tst+ten)/2;

        return min(query_mi(qst,qen,tst,mid,root*2+1),query_mi(qst,qen,mid+1,ten,root*2+2));//搜索左右子树
    }
    int query_sum(int qst,int qen,int tst,int ten,int root)
    {//查询子段和
         push_dowm(root);
        if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝
            return 0;
        if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合
            return segt[root].su;
        int mid =(tst+ten)/2;

        return query_sum(qst,qen,tst,mid,root*2+1)+query_sum(qst,qen,mid+1,ten,root*2+2);//搜索左右子树
    }

    void adjust_elemt(const int addval,const int pos,int st,int en,int root)//点修改,给pos位置的元素加上addval
    {//思路:递归一遍,查找并修改需要修改的元素,然后把路径更新一遍
        if(st==en)
        {
            if(st==pos)
            {//在这里进行元素修改
                segt[root].data+=addval;
                segt[root].mi+=addval;//最小值
                segt[root].su+=addval;//子段和
            }
            return;
        }
        int mid=(st+en)/2;
        if(pos<=mid)
        adjust_elemt(addval,pos,st,mid,root*2+1);
        else
        adjust_elemt(addval,pos,mid+1,en,root*2+2);
        //在这里进行段修改
        segt[root].mi=min(segt[root*2+1].mi,segt[root*2+2].mi);//最小值段修改
        segt[root].su=segt[root*2+1].su+segt[root*2+2].su;//子段和段修改
    }
    void segadd(int qst,int qen,int tst,int ten,int root,int addnum)
    {//全段加
        if(qst>ten||qen<tst)//不符合的区间
            {
                return ;
            }
        if(tst>=qst&&ten<=qen)//找到要加的区间
            {
                segt[root].mark_add+=addnum;
                return ;
            }
        int mid =(tst+ten)/2;
        if(tst>qst&&ten>qen)segt[root].su+=addnum*(qen-tst+1)*addnum;
        else if(tst<qst&&ten<qen)segt[root].su+=addnum*(ten-qst+1)*addnum;
        else segt[root].su+=addnum*(qen-qst+1);
        segadd(qst,qen,tst,mid,root*2+1,addnum);
        segadd(qst,qen,mid+1,ten,root*2+2,addnum);//搜索左右子树
       // segt[root].mark_add+=addthis;
       // return addthis;
    }
    int main()
    {
        const int sz=17;
        int arr[sz+1];
        for(int i=0;i<sz+1;i++)arr[i]=1;
        build(arr,0,0,sz);
        int j=2,cur;
        // adjust_elemt(-1000,4,0,50,0);
        segadd(0,3,0,sz,0,-1);

        for(int i=0;i<=sz;i++)query_mi(i,i,0,sz,0);

        for(int i=0;i<sz*2+1;i++)
        {
            cout<<segt[i].l<<"~"<<segt[i].r<</*":segmin:"<<segt[i].mi<<*/" segsum:"<<segt[i].su<</*" segmark:"<<segt[i].mark_add<<*/" ";
            if(i+2==j)
            {
                cout<<endl;
                j*=2;
            }
        }


        //测试build
        //cout<<query_mi(5,10,0,50,0);
        //cout<<query_sum(0,2,0,50,0);
        return 0;
    }



hdu 1166  敌兵布阵

直接套模板的题,然而神志不清的手滑了半天


#include<cstdio>
#include<iostream>

    using namespace std;
    const int maxn=200000;
    int sz=1;
    //用于存放线段树的结构 l,r表示此区间的左右端点
    struct segn
    {
        int data,l,r,mi,su,mark_add;
    }segt[maxn];
    //建树:叶节点终止条件->递归建左右子树->由左右孩子信息得到此节点信息

    void build(int *dat,int root,int st,int en)
    {

        //st,en表示目前区间的首尾端点序号,dat[]表示用于初始化的数据
        //记录左右端点

        if(st==en)//在叶子节点上加入元素数据
        {

            segt[root].su=dat[st];//求区间和的操作
            return;
        }
        //对于非叶子节点
        int mid=(st+en)/2;//分割点
        //由二叉性,数组存树的左右节点编号分别为此节点的*2+1和*2+2
        build(dat,root*2+1,st,mid);//递归建左子树
        build(dat,root*2+2,mid+1,en);//右子树
        //由左右子树得到此节点的数据
        //求区间最小值

        //求区间和
        segt[root].su=segt[root*2+1].su+segt[root*2+2].su;
    }


    int query_sum(int qst,int qen,int tst,int ten,int root)
    {//查询子段和


        if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝
            return 0;
        if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合
            return segt[root].su;
        int mid =(tst+ten)/2;

        return query_sum(qst,qen,tst,mid,root*2+1)+query_sum(qst,qen,mid+1,ten,root*2+2);//搜索左右子树
    }

    void adjust_elemt(const int addval,const int pos,int st,int en,int root)//点修改,给pos位置的元素加上addval
    {//思路:递归一遍,查找并修改需要修改的元素,然后把路径更新一遍
      /*  if(en>sz)
        {
            int k=0;
            while(++k){k=1;}
        }*/
        if(st==en)
        {
            if(st==pos)
            {//在这里进行元素修改


                segt[root].su+=addval;//子段和
            }
            return;
        }
        int mid=(st+en)/2;
        if(pos<=mid)
        adjust_elemt(addval,pos,st,mid,root*2+1);
        else
        adjust_elemt(addval,pos,mid+1,en,root*2+2);
        //在这里进行段修改

        segt[root].su=segt[root*2+1].su+segt[root*2+2].su;//子段和段修改
    }
    int main()
    {
        int t,n,arr[65536],counn=0;
        string comm;
        scanf("%d",&t);
        while(t--)
        {
            bool flag=true;

            scanf("%d",&n);
            for(int i=0;i<n;i++)
            {
                scanf("%d",&arr[i]);
            }

            sz=n;
            build(arr,0,0,sz-1);
            //for(int i=0;i<2*sz;i++)cout<<segt[i].su<<" ";
            int st,en,ad;
            cout<<"Case "<<++counn<<":"<<endl;
            while(cin>>comm)
            {
                if(comm=="End")break;
                if(comm=="Query")
                {

                     scanf("%d%d",&st,&en);
                    cout<<query_sum(st-1,en-1,0,sz-1,0)<<endl;
                    continue;
                }
                if(comm=="Add"){scanf("%d%d",&st,&ad);}
                if(comm=="Sub"){scanf("%d%d",&st,&ad);ad=-ad;}
                adjust_elemt(ad,st-1,0,sz-1,0);
            }
        }
        return 0;
    }
   

hdu 1754

I Hate It

同样是模板题

 #include <cstdio>
 #include<iostream>

    using namespace std;
    const int maxn=200000*4;
    int Max(int x,int y){return x>=y ? x:y;}
    //用于存放线段树的结构 l,r表示此区间的左右端点
    struct segn
    {
        int data,l,r,mi,su,mark_add;
    }segt[maxn];
    //建树:叶节点终止条件->递归建左右子树->由左右孩子信息得到此节点信息

    void build(int *dat,int root,int st,int en)
    {
        //st,en表示目前区间的首尾端点序号,dat[]表示用于初始化的数据
        //记录左右端点

        if(st==en)//在叶子节点上加入元素数据
        {

            segt[root].mi=dat[st];//求区间最小值的操作

            return;
        }
        //对于非叶子节点
        int mid=(st+en)/2;//分割点
        //由二叉性,数组存树的左右节点编号分别为此节点的*2+1和*2+2
        build(dat,root*2+1,st,mid);//递归建左子树
        build(dat,root*2+2,mid+1,en);//右子树
        //由左右子树得到此节点的数据
        //求区间最小值
        segt[root].mi=Max(segt[root*2+1].mi,segt[root*2+2].mi);
        //求区间和
        segt[root].su=segt[root*2+1].su+segt[root*2+2].su;
    }


    int query_mi(int qst,int qen,int tst,int ten,int root)//查询操作:向下递归的找区间,只要找到被要找的区间包括的区间,就可以肯定这个区间是需要的(因为向下递归的顺序和不交叉的顺序)
    {//查询最小值
        //qst,qen:想查询的区间  tst,ten:正在查的区间

        if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝
            return 0;
        if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合
            return segt[root].mi;
        int mid =(tst+ten)/2;

        return Max(query_mi(qst,qen,tst,mid,root*2+1),query_mi(qst,qen,mid+1,ten,root*2+2));//搜索左右子树
    }

    void adjust_elemt(const int addval,const int pos,int st,int en,int root)//点修改,给pos位置的元素加上addval
    {//思路:递归一遍,查找并修改需要修改的元素,然后把路径更新一遍
        if(st==en)
        {
            if(st==pos)
            {//在这里进行元素修改

                segt[root].mi=addval;//最大值

            }
            return;
        }
        int mid=(st+en)/2;
        if(pos<=mid)
        adjust_elemt(addval,pos,st,mid,root*2+1);
        else
        adjust_elemt(addval,pos,mid+1,en,root*2+2);
        //在这里进行段修改
        segt[root].mi=Max(segt[root*2+1].mi,segt[root*2+2].mi);//最大值段修改

    }

    int main()
    {
        int xss,czs;
        while(~scanf("%d%d",&xss,&czs))
        {
            int arr[200005],cur;
            for(int i=0;i<xss;i++)
            {
                scanf("%d",&cur);
                arr[i]=cur;
            }
            build(arr,0,0,xss-1);
//for(int i=0;i<2*xss;i++)cout<<segt[i].mi<<" ";
            while(czs--)
            {
                char c[4];
                int st,en;
               scanf("%s",c);//cout<<"_____"<<c<<"______";
                if(c[0]=='Q')
                {
                    scanf("%d%d",&st,&en);
                    printf("%d\n",query_mi(st-1,en-1,0,xss-1,0));

                }
                else
                {
                    scanf("%d%d",&st,&en);
                    adjust_elemt(en,st-1,0,xss-1,0);
                   // for(int i=0;i<2*xss;i++)cout<<endl<<segt[i].mi<<" ";
                }
            }
        }






        return 0;
    }

2017.11.14

做题的时候发现模板前面的代码写的太烂,一方面是多余的东西太多,一方面是段修改写错了敲打

拿hdu1698的代码改改补个有段操作的新模板(点查询还没写)(段最值操作也没写

#include <cstdio>
#include<iostream>
    using namespace std;
    const int maxn=500100;
    struct segn
    {
        int su,mark_add;
    }segt[maxn];
   //下推的root是正在改的点的位置,st,en是这个点的区间
    void push_dowm_segadj(int root,int st,int en)//段加的下推
    {
        if(segt[root].mark_add!=0)
        {
            segt[root*2+1].su=((st+en)/2-st+1)*segt[root].mark_add;
            segt[root*2+2].su=(en-(st+en)/2)*segt[root].mark_add;
            segt[root*2+1].mark_add=segt[root].mark_add;
            segt[root*2+2].mark_add=segt[root].mark_add;
            segt[root].mark_add=0;
        }
    }
    void push_dowm_segadd(int root,int st,int en)//段赋值的下推
    {
        if(segt[root].mark_add!=0)
        {
            segt[root*2+1].su+=((st+en)/2-st+1)*segt[root].mark_add;
            segt[root*2+2].su+=(en-(st+en)/2)*segt[root].mark_add;
            segt[root*2+1].mark_add+=segt[root].mark_add;
            segt[root*2+2].mark_add+=segt[root].mark_add;
            segt[root].mark_add=0;
        }
    }
    void build(int *arr,int root,int st,int en)//arr:赋值数组 root,st写0 en写arr的长度-1
    {
        
        if(st==en)
        {
            segt[root].mark_add=0;
            segt[root].su=arr[st];
            return;
        }

        int mid=(st+en)/2;
        build(arr,root*2+1,st,mid);
        build(arr,root*2+2,mid+1,en);

        segt[root].su=segt[root*2+1].su+segt[root*2+2].su;
    }


    void segadj(int qst,int qen,int tst,int ten,int root,int addnum)
    {//段赋值 q:查询区间  t:正在搞的区间,写0和n-1  addnum;改成的值

        if(qst>ten||qen<tst)//不符合的区间
            {
                return ;
            }
        if(tst>=qst&&ten<=qen)//找到要改的区间
            {
                segt[root].mark_add=addnum;
                segt[root].su=addnum*(ten-tst+1);
                return ;
            }
        int mid =(tst+ten)/2;
        push_dowm_segadj(root,tst,ten);
        segadj(qst,qen,tst,mid,root*2+1,addnum);
        segadj(qst,qen,mid+1,ten,root*2+2,addnum);//搜索左右子树
    }
        void segadj(int qst,int qen,int tst,int ten,int root,int addnum)
    {//段修改 q:查询区间  t:正在搞的区间,写0和n-1  addnum;改成的值

        if(qst>ten||qen<tst)//不符合的区间
            {
                return ;
            }
        if(tst>=qst&&ten<=qen)//找到要加的区间
            {
                segt[root].mark_add=addnum;
                segt[root].su=addnum*(ten-tst+1);
                return ;
            }
        int mid =(tst+ten)/2;
        push_dowm_segadd(root,tst,ten);
        segadd(qst,qen,tst,mid,root*2+1,addnum);
        segadd(qst,qen,mid+1,ten,root*2+2,addnum);//搜索左右子树
    }
    /*
    void upd(int root,int st,int en)//对于全部改完后才进行查询的题,可以最后进行整体下推
    {
        if(st==en)return;
        push_dowm(root,st,en);//按需求改这个下推
        upd(2*root+1,st,(st+en)/2);
        upd(2*root+2,(st+en)/2+1,en);
        segt[root].su=segt[root*2+1].su+segt[root*2+2].su;
    }
    */





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值