【原創】 線段樹學習總結

    經過了這幾天的學習---線段樹---收穫着實不小,不過解稍微難一點的題還是會有超時的可能,但也算是入門了。我記得在這段時間的學習中,我保持了一個良好的習慣---剛開始的時候不要在網上搜解題報告。這樣可以訓練你的思考能力,有時候肯能還會和別人的不一樣,學習起來就不是那麼死板了。這幾天的學習大致可以分爲4個階段(區間統計,單點更新,區間更新,區間合併,掃描線(這個只瞭解到了初級的))。

區間統計:
    這類型的題目不對節點更新,只是實現的統計的功能(有時候也會伴隨着一些更新)。比如統計一個區間的最大值(最小值),統計逆序數(當然樹狀數組更好一些)等這個題一般的做法是在建樹的同時,利用回溯的過程就把區間的信息統計出來。這類題適合於初學者。
    相應的練習題:
    hdu 1394 Minimum Inversion Number
    hdu 1698 Just a Hook
    hdu 2492 Ping pong
單點更新:
    其實單點更新顧名思義就是每次操作的時候對葉子節點操作,但是常規的訪問都是以區間的形式。做這類題的時候只需要在更新的時候用update(index)實現更新就可以了,沿着葉子節點向上更新,用遞歸實現比較簡單。這類題適合於初學者。
hdu 1754 I hate it
代碼:

#include<stdio.h>
#define MAXN 200005

struct Tree
{
        int Max,l,r,m;
}ar[4*MAXN];

int n,m,h[MAXN];

int BuildTree(int index,int begin,int end)
{
        int x,y;
        ar[index].l=begin;ar[index].r=end;
        ar[index].m=(begin+end)>>1;
        if(begin<end)
        {
                x=BuildTree(2*index,begin,ar[index].m);
                y=BuildTree(2*index+1,ar[index].m+1,end);
                ar[index].Max=x>y?x:y;
        }
        else
                ar[index].Max=h[begin];
        return ar[index].Max;
}

int GetMax(int index,int begin,int end)
{
        if(ar[index].l==begin && ar[index].r==end)
        {
                return ar[index].Max;
        }
        else if(begin>ar[index].m)
        {
                return GetMax(index*2+1,begin,end);
        }
        else if(end<=ar[index].m)
        {
                return GetMax(index*2,begin,end);
        }
        int x,y;
        x=GetMax(index*2,begin,ar[index].m);
        y=GetMax(index*2+1,ar[index].m+1,end);
        return x>y?x:y;
}

void ChangeTree(int index,int x,int y)
{
        if(ar[index].Max<y)
        {
                ar[index].Max=y;
        }
        if(ar[index].l<ar[index].r)
        {
                if(x<=ar[index].m)
                {
                        ChangeTree(index*2,x,y);
                }
                else
                {
                        ChangeTree(index*2+1,x,y);
                }
        }
}

void init()
{
        int i;
        for(i=1;i<=n;i++)
        {
                scanf("%d",&h[i]);
        }
        BuildTree(1,1,n);
}

void make()
{
        char op[10];
        int x,y;
        while(m--)
        {
                scanf("%s%d%d",op,&x,&y);
                if(op[0]=='Q')
                {
                        printf("%d\n",GetMax(1,x,y));
                }
                else
                {
                        ChangeTree(1,x,y);
                }
        }
}

int main()
{
        while(scanf("%d%d",&n,&m)!=EOF)
        {
                init();
                make();
        }
        return 0;
}
相應的練習題:
hdu 1166 敵兵佈陣


    剛開始的時候,可能有人會想到是否可以用單點更新的思想來做呢?按理來說是可以的,但是有時候往往我們代碼的效率就不夠好了。這樣想,我們可以之間更新區間,但是當我們訪問區間下面的節點的時候,不是已經改變了嗎?所以我們還得在查詢的時候,相應的更新。也叫做延遲更新。這類題適合於中等偏下的題,花不了多少時間。
相應的練習題:
hdu 4107 Gangster
hdu 3397 Sequence operation


    這類題最常見的題是:求一個區間連續的一些性質,比如1-n數組裏,要麼放黑棋,要麼放白棋,然後給你一個詢問區間[i,j],問你這個區間裏黑棋連續最多的爲多少。比如最大連續上升(很多時候有伴隨着一些改變)等。常見的解法是:在節點裏添加連個變量,l,r分別記錄,左右連續的個數,然後在依次不斷的向上,不斷的更新。
這類題比較多,給一個題的代碼:
hdu 3308 LCIS


#include<stdio.h>
#define MAXN 100005

struct data
{
        int l1,l2,r2,r1;
};

struct node
{
        int begin,end,mid;
        int num;
        data p;
}tree[MAXN<<2];

int n,m,st[MAXN],ans;

void SetValue(data &p,int l1,int l2,int r2,int r1)
{
        p.l1=l1;
        p.l2=l2;
        p.r2=r2;
        p.r1=r1;
}

int Max(int x,int y)
{
        return x>y?x:y;
}

void update(int index)
{
        int x=0;
        data p=tree[index<<1].p,q=tree[index<<1 | 1].p;
        if(st[p.r1]<st[q.l1])
        {
                x=q.l2-p.r2+1;
        }
        tree[index].num=Max(x,Max(tree[index<<1].num,tree[index<<1 | 1].num));
        if(p.l2+1==q.l1 && st[p.l2]<st[q.l1])
        {
                p.l2=q.l2;
        }
        if(p.r1+1==q.r2 && st[p.r1]<st[q.r2])
        {
                q.r2=p.r2;
        }
        p.r2=q.r2;
        p.r1=q.r1;
        tree[index].p=p;
}

void BuildTree(int index,int begin,int end)
{
        tree[index].begin=begin;tree[index].end=end;
        tree[index].mid=(begin+end)>>1;
        if(begin<end)
        {
                int x=0;
                BuildTree(index<<1,begin,tree[index].mid);
                BuildTree(index<<1 | 1,tree[index].mid+1,end);
                update(index);
        }
        else
        {
                tree[index].num=1;
                scanf("%d",&st[end]);
                SetValue(tree[index].p,end,end,end,end);
        }
}

void Insert(int index,int x)
{
        if(tree[index].begin==tree[index].end)
        {
                return;
        }
        if(x<=tree[index].mid)
        {
                Insert(index<<1,x);
        }
        else
        {
                Insert(index<<1 | 1,x);
        }
        update(index);
}

data get(int index,int begin,int end)
{
        data p,q;
        if(tree[index].begin==begin && tree[index].end==end)
        {
                if(ans<tree[index].num)
                {
                        ans=tree[index].num;
                }
                return tree[index].p;
        }
        if(end<=tree[index].mid)
        {
                return get(index<<1,begin,end);
        }
        else if(begin>tree[index].mid)
        {
                return get(index<<1 | 1,begin,end);
        }
        p=get(index<<1,begin,tree[index].mid);
        q=get(index<<1 | 1,tree[index].mid+1,end);
        int x=0;
        if(st[p.r1]<st[q.l1])
        {
                x=q.l2-p.r2+1;
        }
        ans=Max(x,ans);
        if(p.l2+1==q.l1 && st[p.l2]<st[q.l1])
        {
                p.l2=q.l2;
        }
        if(p.r1+1==q.r2 && st[p.r1]<st[q.r2])
        {
                q.r2=p.r2;
        }
        p.r2=q.r2;
        p.r1=q.r1;
        return p;
}

void make()
{
        BuildTree(1,0,n-1);
        int x,y;
        char op[5];
        while(m--)
        {
                scanf("%s%d%d",op,&x,&y);
                if(op[0]=='Q')
                {
                        ans=0;
                        get(1,x,y);
                        printf("%d\n",ans);
                }
                else
                {
                        st[x]=y;
                        Insert(1,x);
                }
        }
}

int main()
{
        int t;
        scanf("%d",&t);
        while(t--)
        {
                scanf("%d%d",&n,&m);
                make();
        }
        return 0;
}
相應的練習題:
hdu 3911 Back And White
hdu 4046 Panda

    這一階段主要求的是一些矩形的面積並,周長並,有時候也會有體積並。做這類的題,一般會用到離散化的思想。比如你要插入[4999,6000]這個區間到線段樹中,那麼你只需要把4999和6000分別映射到兩個更小的數x,y然後插入。然後一次掃描線段就可以了。
今天上午寫了一個代碼:
hdu 1255 覆蓋的面積


#include<stdio.h>
#include<cmath>
#include<algorithm>
#define MAXN 2005

using namespace std;

double flag[MAXN];

struct data
{
        double y1,y2,x,f;
}line[MAXN];

struct node
{
        int begin,end,mid,cover;
        double x;
}st[MAXN<<2];

int n;

int cmpdouble(double x,double y)
{
        return x<y;
}

int cmpd(data p,data q)
{
        return p.x<q.x;
}

void BuildTree(int t,int begin,int end)
{
        st[t].begin=begin;st[t].end=end;
        st[t].mid=(begin+end)>>1;
        st[t].cover=0;
        st[t].x=0;
        if(begin>=end-1)
                return;
        BuildTree(t<<1,begin,st[t].mid);
        BuildTree(t<<1 | 1,st[t].mid,end);
}

void init()
{
        int i,l,r;
        double x1,x2,y1,y2;
        for(i=0;i<n;i++)
        {
                scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
                l=i<<1;r=l+1;
                flag[l]=y1;
                line[l].x=x1;
                line[l].y1=y1;
                line[l].y2=y2;
                line[l].f=1;
                flag[r]=y2;
                line[r].x=x2;
                line[r].y1=y1;
                line[r].y2=y2;
                line[r].f=-1;
        }
        n=r;
        sort(flag,flag+n+1,cmpdouble);
        sort(line,line+n+1,cmpd);
        BuildTree(1,0,n);
}

int search(double x)
{
        int l=0,r=n,mid;
        double y;
        while(l<=r)
        {
                mid=(l+r)>>1;
                if(fabs(flag[mid]-x)<1e-8)
                        return mid;
                else if(flag[mid]<x)
                {
                        l=mid+1;
                }
                else
                {
                        r=mid-1;
                }
        }
        return -1;
}

double update(int t,int begin,int end,double x,int f)
{

        if(begin>=st[t].end || end<=st[t].begin)
                return 0;
        if(st[t].begin+1==st[t].end)
        {
                double ans;
                if(st[t].cover>1)
                {
                        ans=(x-st[t].x)*(flag[st[t].end]-flag[st[t].begin]);
                        st[t].x=x;
                        st[t].cover+=f;
                        return ans;
                }
                else
                {
                        st[t].cover+=f;
                        st[t].x=x;
                        return 0;
                }
        }
        return update(t<<1,begin,end,x,f)+
                update(t<<1 | 1,begin,end,x,f);
}
void make()
{
        double sum=0;
        int i,l,r;
        for(i=0;i<n;i++)
        {

                l=search(line[i].y1);
                r=search(line[i].y2);
                sum+=update(1,l,r,line[i].x,line[i].f);
        }
        printf("%.2lf\n",sum);
}

int main()
{
        int t;
        scanf("%d",&t);
        while(t--)
        {
                scanf("%d",&n);
                init();
                make();
        }
        return 0;
}

其實還可以優化,留給自己慢慢完成!!!!

相應的練習:
hdu 3642 Get The Treasury(體積合併)
    這幾天的練習總體說來效果還是可以,至少解決了很多自己不能完成的東西。其實還有其他的線段樹解法,它不是常規的。比如:hdu 4007 Dave。
    第一次近距離接觸線段樹,希望自己後面能更好的理解線段樹。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章