用樹解決K-th Number之主席樹
K-th Number
1.主席樹解法
前方高能,你需要(一些線段樹姿勢)
答記者問
Q:為什麼叫主席樹?叫主席樹會不會給人一種欽定的感覺?
A:因為某大佬考場上忘記怎麼寫劃分樹了,於是當場yy出來了,叫主席樹跟樹本身特徵沒關係(至少我這麼理解),是因為那個大佬叫hjt,我說的意思你懂吧。
Q:什麼是主席樹?
A:我分析演算法都喜歡按照基本法,從暴力的來,比如哦,要線上詢問前 k次修改後的線段樹()的內容怎麼辦辦啊。
(鳳凰臺記者)我知道,把修改過的全記下來啊。
唉,西方的那個暴力我沒有見過,不知道比你們高到哪裡去了。主席樹就是暴力啊,只不過你看看是不是每次修改只有從根到該節點的值改了,我只要新建這些點就行呢
Q:這題怎麼用呢?
A:就把N長的數列分成N個字首,記錄到N棵線段樹裡面,其實就相當於上一棵字首樹(root[N-1])+這個數(A[N])組成的新樹=這棵字首樹(root[N])(離散化是必須的!),求區間第k大就,比較頭(l-1)尾(r)兩個線段樹(對,其實就是兩棵完完整整的線段樹),然後你可以比較它們之間有什麼差別=-=!!!(真實的故事)數量差別(T[x] - T[Y])就是之間的數(你要求第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;
}
人生經驗
如果你解決本題有什麼人生經驗(程式錯誤,我的錯誤,不懂的地方,奇奇怪怪的優化想法)可以留言偶!,我會選一些掛上。