線段樹學習筆記及模板

概括:線段樹是一種數據結構,針對需要動態 修改並且獲取數據中一段信息(子段和,子段最小值等)的數據來使用

大體思路:使用一顆完全二叉樹(數組實現)記錄數據。樹的葉子節點記錄每一個數據元素,非葉子節點記錄段信息。

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





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章