劃分樹小結

來源:互聯網
上載者:User

最近學習了一下劃分樹,下面總結一下。

我們在求區間最值的時候,一般可以用線段樹解決,但是如果要求區間第k小或者第k大值的話線段樹就有點力不從心了,這是我們可以用劃分樹來解決。劃分樹利用了快速排序的思想,首先是建樹,我們設當前區間的中位元為mid,(為了能快速找到區間的中位元,我們一般先對原序列做一次排序)則我們將區間中比mid小的放入左子樹,將區間中比mid大數的放入右子樹中,和mid相等的要討論一下,有些需要放到左子樹中,其他的放到右子樹中,注意我們將數字放入子樹的時候其相對順序是不變的。這樣我們一層一層下去,每次區間都減半,則空間消耗為O(nlogn)。下面看一個例子。

假設序列長度為9,依次為
3 5 7 3 4 9 4 2 5,怎我們看看建樹完成後是什麼樣子。

sort[
][2  3  3  4  4  5  5  7  9]

tree[0][3  5  7  3  4 
9  4  2  5]

tree[1][3  3  4  4  2][5  7 
9  5]

tree[2][3 
3  2][4  4][5  5][7 
9]

tree[3][3  2][3][4][4][5][5][7][9]

tree[4][2][3][3][4][4][5][5][7][9]

好了,樹建完了,接下來就是最關鍵的查詢了,我們設函數query(p,l,r,s,t,k)表示在第p層子樹區間範圍在[l,r]的子樹中尋找區間[s,t]中的第k小值。我們在每一顆子樹中設sum[i]表示區間[l,i]範圍內有多少個數字被放到了左子樹中,那麼我們容易得到,sum[t]-sum[s-1]表示在區間[s,t]有多少個樹被放入了左子樹中,我們不妨設這個值為num,若k<=num,我們就可以知道我們要找的數一定在左子樹中,否則一定在右子樹中,我們接下來只要繼續往下遍曆,當l==r的時候我們就可以確定我們要找的數,容易知道這一步驟的複雜度為O(log
n),現在關鍵的一點就是如何再往下便利的時候確定ls,t的值。這個其實自己畫畫圖就很容易推出來的,下面唯寫結論,這裡先設區間[l,s-1]中被放入左子樹的數有snum個,當前區間的中點為mid,

則若k<=num,我們返回query(p+1,l,mid,l+snum,l+sum[t]-1,k),(要找的數在左子樹上)

否則,我們返回query(p+1,mid+1,r,mid+1-l+s-snum,mid+1-l+t-sum[t],k-num);(要找的數在右子樹上)

下面再舉個例子,數和上面一樣

我們現在要找到區間[2,7]中的第3小數。

sort[
][2  3  3  4  4  5  5  7  9]

tree[0][3 
5  7  3  4 
9  4  2  5]

tree[1][3 
3  4  4  2][5  7 
9  5]

tree[2][3 
3  2][4  4][5  5][7 
9]

tree[3][3  2][3][4][4][5][5][7][9]

tree[4][2][3][3][4][4][5][5][7][9]

以上橙黃色背景的數字即為我們要求的範圍。

我們首先在第一層樹上尋找,即query(0,1,9,2,7,3),我們發現在[2,7]區間中有3個數被放入了左子樹,滿足k<=num,所以我們往左子樹中找,調用query(1,1,5,2,4,3)。

在第二層樹中我們發現區間[2,4]只有一個數被放入左子樹,所以我們要找的數一定在右子樹中,調用query(2,4,5,4,5,2)。

在第三層樹中,我們發現區間[4,5]只有一個數被放入左子樹,同理我們應該往右子樹找,調用query(3,5,5,5,5,1)。現在可以發現l==r,則我們可以確定已經找到了要找的數,則返回4即可,我們可知在區間[2,7]上第3小的數為4。

基礎的劃分樹就到這裡,下面給出My Code:

#include <iostream>#include <string.h>#include <stdio.h>#include <algorithm>#define maxn 100010#define mid ((l+r)>>1)using namespace std;int t[20][maxn],sum[20][maxn];int a[maxn],as[maxn];//以下為尋找區間第k小劃分樹void build(int p,int l,int r){    int lm=0,i,ls=l,rs=mid+1;//lm表示應被放入左子樹且與中位元相等的數有多少個,ls為左子樹的起始位置,rs為右子樹的起始位置    for(i=mid;i>=l;i--) //求lm    {        if(as[i]==as[mid])        lm++;        else        break;    }    for(i=l;i<=r;i++)    {        if(i==l)//這裡要特殊討論        sum[p][i]=0;        else        sum[p][i]=sum[p][i-1];        if(t[p][i]==as[mid])//若與中位元相等則判斷是否應該被放入左子樹        {            if(lm)            {                lm--;                sum[p][i]++;                t[p+1][ls++]=t[p][i];            }            else            t[p+1][rs++]=t[p][i];        }        else if(t[p][i]<as[mid])//尋找區間第K大即為>        {            sum[p][i]++;            t[p+1][ls++]=t[p][i];        }        else        t[p+1][rs++]=t[p][i];    }    if(l==r)    return;    build(p+1,l,mid);    build(p+1,mid+1,r);}int query(int p,int l,int r,int ql,int qr,int k){    int s,ss;//s表示l到ql-1的區間內放入左子樹的個數,ss表示區間[ql,qr]被放入左子樹的個數    if(l==r)//找到所求的數    return t[p][l];    if(ql==l)    s=0,ss=sum[p][qr];    else    s=sum[p][ql-1],ss=sum[p][qr]-s;    if(k<=ss)//要找的數在左子樹中    return query(p+1,l,mid,l+s,l+sum[p][qr]-1,k);    else//要找的數在右子樹中    return query(p+1,mid+1,r,mid+1-l+ql-s,mid+1-l+qr-sum[p][qr],k-ss);}

下面是兩道劃分樹的模板題

poj
2104

題解這裡有http://blog.csdn.net/dyx404514/article/details/8732257

hdu
4417

題解這裡有:http://blog.csdn.net/dyx404514/article/details/8732468




聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.