线段树学习笔记及模板

来源:互联网 发布:魔卡幻想 淘宝 编辑:程序博客网 时间:2024/05/29 17:44

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

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

建树,查询,修改都由二叉树的性质:第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;    }    */





原创粉丝点击