1. 程式人生 > >用樹解決K-th Number之主席樹

用樹解決K-th Number之主席樹

K-th Number

1.主席樹解法

前方高能,你需要(一些線段樹姿勢)

答記者問

Q:為什麼叫主席樹?叫主席樹會不會給人一種欽定的感覺?
A:因為某大佬考場上忘記怎麼寫劃分樹了,於是當場yy出來了,叫主席樹跟樹本身特徵沒關係(至少我這麼理解),是因為那個大佬叫hjt,我說的意思你懂吧。

Q:什麼是主席樹?
A:我分析演算法都喜歡按照基本法,從暴力的來,比如哦,要線上詢問前 k次修改後的線段樹()的內容怎麼辦辦啊。
(鳳凰臺記者)我知道,把修改過的全記下來啊。
唉,西方的那個暴力我沒有見過,不知道比你們高到哪裡去了。主席樹就是暴力啊,只不過你看看是不是每次修改只有從根到該節點的值改了,我只要新建這些點就行呢

你們啊,naive,會有K棵樹,MLE了,還是O(Kn)的,主席樹只要O(nlogn),不超時,不超空間(nlogn 習慣性線段樹*4 主席樹*40),中央是資磁的。

Q:這題怎麼用呢?
A:就把N長的數列分成N個字首,記錄到N棵線段樹裡面,其實就相當於上一棵字首樹(root[N-1])+這個數(A[N])組成的新樹=這棵字首樹(root[N])(離散化是必須的!),求區間第k大就,比較頭(l-1)尾(r)兩個線段樹(對,其實就是兩棵完完整整的線段樹),然後你可以比較它們之間有什麼差別=-=!!!(真實的故事)數量差別(T[x] - T[Y])就是之間的數(你要求第k大的區間的數量)產生

的,所以你比較,如果k小於等於左子樹的差,說明第k大在左邊,向左走,不然向右並且減掉左子樹的差)。

Q:為什麼是比較兩樹值差呢?
A:差代表了這段區間啊,西方的哪個字首不是這麼玩的,不知道妙到哪裡去了,差其實是為這個區間再建的一棵字首樹的值
線段樹(root[l->r])=字首樹(root[r])-字首樹(root[l-1])

Q:(naive記者)那麼,先生那有沒有其他想法呢?
A:當然是劃分樹和整體二分啦,什麼時候貼劃分樹和整體二分程式碼要看中央的決定。

先自己試試?

poj2104

Sample Input
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3

Sample Output
5
6
3

我這麼說你們可能不懂,看程式碼!!!

總結:

1.離散化 (排序O(nlogn) 其他O(nlogn)用stl太玄學了,超時不存在的)
2.字首樹 (O(nlogn))
3.在l~r樹上查詢第k大的樹 (字首樹->l~r樹 O(1),查詢O(logn))

下發檔案

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#include<vector>
#define N 100010
using namespace std;
vector <int> v;     //步驟1           
int n, m, a[N], root[N], cur;

struct node{        //步驟2
    int l, r, sum;  
}T[N*40];           //步驟2

int getid(int x){return lower_bound(v.begin(),v.end(),x) - v.begin() + 1;}  //步驟1
void build(int l, int r, int& x, int y, int pos)//步驟2
{ 
    T[++cur] = T[y]; x = cur; T[x].sum++;
    if(l == r) return ;
    int mid;
    mid = (l+r) >> 1;
    if(mid >= pos)// 我在這錯過,大家小心
        build(l, mid, T[x].l, T[y].l, pos);
    else
        build(mid+1, r, T[x].r, T[y].r, pos);
    return;
}                                       //步驟2

int query(int l ,int r, int x, int y,int k)//步驟3
{
    if(l == r)return l;
    int sum, mid;
    mid = (l+r)>>1;
    sum = T[T[y].l].sum - T[T[x].l].sum;
    if(sum >= k)//小心,可能跟你習慣不一樣
        query(l, mid, T[x].l, T[y].l, k);
        else
        query(mid+1, r, T[x].r, T[y].r, k-sum);
}           //步驟3
void init()//init
{
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i++) scanf("%d",&a[i]), v.push_back(a[i]);//步驟1
    sort(v.begin(), v.end());//步驟1 v.erase(unique(v.begin(),v.end()),v.end());//步驟1
    for(int i = 1; i <= n; i++) build(1, n, root[i], root[i-1], getid(a[i]));//步驟2
    return ;    
}
void work()
{   int x, y, k;
    for(int i = 1; i <= m; i++) scanf("%d%d%d",&x,&y,&k),printf("%d\n",v[query(1,n,root[x-1],root[y],k)-1]);//步驟3
    return ;
}
int main()
{
    init();
    work();
    return 0;
}

人生經驗

如果你解決本題有什麼人生經驗(程式錯誤,我的錯誤,不懂的地方,奇奇怪怪的優化想法)可以留言偶!,我會選一些掛上。