1. 程式人生 > >hdu2665 求區間第k大(小?)【主席樹or可持久化線段樹or函式式線段樹】

hdu2665 求區間第k大(小?)【主席樹or可持久化線段樹or函式式線段樹】

題目大意:感覺題目表述得不明不白的,給一堆不知道我也不知道什麼資料範圍的數,然後給你M個區間,輸出每個區間的第k大的數(這裡出現嚴重的問題!!!)
題目說得kth bigger 難道不是第k大?結果我WA了一堆之後,翻了幾篇別人的部落格程式碼,結果發現別人好像都是求第k小 AC的。。。。然後換成求第k小,果然過了。。。。。。。。。。。。。。
大概是我英語不好。。。或者程式碼本來就打挫了。。。。

總之,大體的思路是用主席樹,不過據說劃分樹時間更快記憶體更小,然而我並不會(鹹魚一會就去補)。。

剛敲的時候MLE了幾次,因為多了一些無關緊要的變數。。
然後因為不知道給的是什麼範圍的資料,保險起見,做了離散化處理。。。。

OK ,進入正題。。這棵坑爹的主席樹。。

主席樹(或者叫可持久化線段樹、函式式線段樹),基本的思想其實就是對於【1,n】區間裡所有的字首區間(【1,1】,【1,2】。。。。。【1,n】),都給這些區間建立一個線段樹,也就是說,需要n棵線段樹。
注意,不是在上述的字首裡繼續劃分,而是以這些區間裡的數為基礎建立線段樹。

好,為了方便表述,我們定義小寫字母代表我原本長度為n的那堆數所對應的區間,比如【1,j】
用大寫字母來表示我所建立的線段樹上的結點所對應的區間,比如結點T上的區間是【L,R】
一個是原本的序列的,一個是樹上的,可能有點繞,不急,後面還有更繞的。。。

線段樹上區間【L,R】儲存的應該是數的大小(經過離散化之後的,也就是第L大到第R大的數),也就是說,第j棵線段樹應該存的是【1,j】區間裡,數的大小在【L,R】這個範圍內的個數有VAL個;
舉個慄紙:
1 2 3 4 5 一共5個數
第一棵線段樹 只儲存 1 這個序列

對應的線段樹
第二棵 是 1 2 這個序列對應的線段樹
以此類推是
1 2 3
1 2 3 4
1 2 3 4 5

而在第一棵樹上,區間【1,5】 的值應該是1,因為在序列字首裡1到5的值只出現了1次
第二棵樹上,區間【1,5】的值會是2,因為1、2這兩個數都在【1,5】的範圍內;
第三棵樹也如此,往下推一下,第三棵樹的區間【1,2】的值是2,區間【3】的值是1;
以此類推。。

好了,現在問題來了,這樣子要建n棵線段樹,記憶體炸得不要不要的;
假如road【j】代表序列的第j個數(離散化過的!!)
但細心觀察下,可以發現,在【1,j】和【1,j+1】這兩個字首之間的差異僅僅是一個road【j+1】,也就是說,在【1,j】的樹上,假如road【j+1】這個值是在【1,j】的左子樹的那些區間上的,那麼【1,j】和【1,j+1】對應樹的右子樹應該是一樣的;

比如剛才舉的那個慄紙,對於j=4的情況,【1,j】和【1,j+1】
當我需要建立第五棵線段樹的時候,
這棵線段樹上,【1,5】的值,比前一棵的多1,
在【1,3】這個區間的值上,與前一棵樹對應區間的值完全一樣,而【1,3】區間子樹也是如此;

不難發現,其實每次往這些線段樹裡新增一個數值的時候,我們並不需要重新新建一棵完整的樹,取而代之的是可以僅僅新建logn個結點,其它結點與上一個字首區間共享

上圖
這裡寫圖片描述

好,到現在為止,我們就可以得到【1,j】這個區間裡,【L,R】範圍的數的數目了,然後只要求一個【1,i-1】的【L,R】範圍的數的數目,兩者相減就是【i,j】的【L,R】範圍的數的數目了;
然後我們知道了這個範圍的數目,就可以通過在左右子樹裡準確地找到第K小的數了(具體實現下面列舉)。

主席樹的實現:
因為每個結點只要新增logn個結點,所以n個結點的空間複雜度就是nlogn
為了方便操作,先建立一棵空的線段樹,因為有n個數,離散化之後所有的數肯定都在【1,n】這個範圍以內的,也就是說,每棵樹對應位置的結點表示的區間都是相同的;(剛開始就是因為智障了多開了兩個變數LR來表示區間,導致MLE。。。orz)
插入新結點的時候,可以用新結點和上一棵樹對應位置的結點同時進行遞迴,就可以實現每個位置都共享左孩或者右孩了;
在進行查詢的時候,對於區間【i,j】,應該同時對第i棵樹和第j棵樹進行遞迴,在尋找第k小的樹時,先求出兩棵樹的當前區間【L,R】的左半部分割槽間(也就是左孩)對應數值的差,這樣可以得出【i,j】裡,【L,M】的範圍的數的個數,然後判斷一下k是否大於這個值,小於或者等於的話,說明第k大的數就在左孩區間裡,這時候往左孩裡走;大於的話,說明第k大不在這個區間裡,而在【M+1,R】這個範圍裡,因為【L,M】的個數都不夠k大,這時候應該要往右孩區間裡找第k-num(【L,M】)小的數,因為左孩區間裡已經有這麼多個數比k小了,往右孩出發的時候就得先減去這些的個數。

大體上如此,如有錯誤請斧正

AC程式碼:

#include <bits/stdc++.h>

using namespace std;

int tot;
int n;
struct tr
{
    int val;
    int lc;
    int rc;
}tree[100001*20];

int node[100001];

int hah[100001];

int save[100001];

void build(int l,int r)
{
    int id=tot;
    if(l==r)
        return ;
    int m=(l+r)>>1;
    tree[id].lc=++tot;
    build(l,m);
    tree[id].rc=++tot;
    build(m+1,r);
}

void push(int x,int old,int las,int l,int r)
{
    int m=(l+r)>>1;
    tree[las].val=tree[old].val+1;
    if(l==r)
        return ;
    if(x<=m)
    {
        tree[las].rc=tree[old].rc;
        tree[las].lc=++tot;
        push(x,tree[old].lc,tot,l,m);
    }
    else
    {
        tree[las].lc=tree[old].lc;
        tree[las].rc=++tot;
        push(x,tree[old].rc,tot,m+1,r);
    }
}

int query(int s,int t,int k,int l,int r)
{
    if(l==r)
        return l;
    int cnt=tree[tree[t].lc].val-tree[tree[s].lc].val;
    int m=(l+r)>>1;
    if(k<=cnt)
    {
        return query(tree[s].lc,tree[t].lc,k,l,m);
    }
    else
    {
        return query(tree[s].rc,tree[t].rc,k-cnt,m+1,r);
    }
}


int shit()
{
    int s;
    int t;
    int k;
    scanf("%d %d %d",&s,&t,&k);
    //k=t-s+2-k;
    return query(node[s-1],node[t],k,1,n);
}


int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int m;
        scanf("%d %d",&n,&m);
        memset(tree,0,sizeof(tree));
        tot=1;
        build(1,n);
        int cnt=1;
        map<int,int> mp;
        for(int i=1 ; i<=n ; i++)
        {
            scanf("%d",&hah[i]);
            save[i]=hah[i];
        }
        sort(save+1,save+n+1);
        for(int i=1 ; i<=n ; i++)
        {
            if(mp[save[i]]==0)
                mp[save[i]]=cnt++;
        }
        for(int i=1 ; i<=n ; i++)
        {
            hah[i]=mp[hah[i]];
        }
        unique(save+1,save+n+1);
        node[0]=1;
        for(int i=1 ; i<=n ; i++)
        {
            node[i]=++tot;
            push(hah[i],node[i-1],node[i],1,n);
        }
        for(int i=0 ; i<m ; i++)
        {
            int t=shit();
            printf("%d\n",save[t]);
        }
    }
    return 0;
}