主席樹入門詳解二(學習筆記)(例題SPOJ
主席樹入門詳解一連結
Start~
看了前一篇部落格,應該已經對最基礎的主席樹有了一個大概的掌握。主席樹的本質就是一堆線段樹的集合(也就是包含歷史版本的線段樹),所以需要用一堆線段樹來解決的問題,就可以用主席樹來解決。主席樹與線段樹最大的區別就是主席樹的左右兒子的節點編號是不固定的。那麼我們在編寫程式碼的時候,傳入根節點的座標,然後再記錄左右兒子的座標,這樣我們的查詢,更新函式,都和普通的線段樹差不了多少,關鍵就是節點的公用關係,和線段樹在題目中的意義和用法!(主席樹在程式碼上和線段樹差不了多少,主要就是節點的不同!)
那麼就開始這次的題目:求區間數字的種類數
題目連結:
題目:
Given a sequence of n numbers a1, a2, ..., an and a number of d-queries. A d-query is a pair (i, j) (1 ≤ i ≤ j ≤ n). For each d-query (i, j), you have to return the number of distinct elements in the subsequence ai, ai+1, ..., aj.
Input
- Line 1: n (1 ≤ n ≤ 30000).
- Line 2: n numbers a1, a2, ..., an (1 ≤ ai ≤ 106).
- Line 3: q (1 ≤ q ≤ 200000), the number of d-queries.
- In the next q lines, each line contains 2 numbers i, j representing a d-query (1 ≤ i ≤ j ≤ n).
Output
- For each d-query (i, j), print the number of distinct elements in the subsequence ai, ai+1, ..., aj in a single line.
Example
Input 5 1 1 2 1 3 3 1 5 2 4 3 5 Output3 2 3
題目大意:
給你 n 個數,然後有 q 個詢問,每個詢問會給你[l,r],輸出[l,r]之間有多少種數字。
題目分析:
首先我們還是思考對於右端點固定的區間(即R確定的區間),我們如何使用線段樹來解決這個問題。
我們可以記錄每個數字最後一次出現的位置。比如,5這個數字最後一次出現在位置3上,就把位置3記錄的資訊++(初始化為0)。比如有一個序列 1 2 2 1 3 那麼我們記錄資訊的數列就是 0 0 1 1 1 (2最後出現的位置是位置3 1最後出現的位置是位置4 3最後出現的位置是位置5)。那麼對區間 [1,5] , [2,5] , [3,5] , [4,5] , [5,5]的數字種數,我們都可以用sum[5]-sum[x-1]來求(sum陣列記錄的是字首和)(字首和之差可以用線段樹或者樹狀陣列來求)。
那麼對著區間右端點會變化的題目,我們應該怎麼辦呢?先思考一下如果右端點有序的話,我們可以怎麼做。對R不同的區間,向線段樹或者樹狀陣列中新增元素,知道右端點更新為新的R,在新增的過程中,如果這個元素之前出現過,就把之前記錄的位置儲存的資訊 -1,然後在新的位置儲存的資訊 +1,這樣就可以保證在新的右端點固定的區間裡,記錄的是數字最後一次出現的位置的資訊,這樣題目就解決了。
也就是說對於這個題目,我們也可以不用主席樹,只要對詢問排序,然後利用樹狀陣列或者線段樹就可以解決這個問題。(離線解法)
如果不對詢問排序的話,我們就必須要用主席樹來解決這個問題了,對每個右端點建立一個線段樹。不斷查詢即可。(線上解法)
程式碼:
1.線上~主席樹(理解每個線段樹的寫法(查詢,更新)程式碼一看就明白了)
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <map>
using namespace std;
const int MAXN=30010;
struct node{
int l,r,sum;
}T[MAXN*40];
int cnt,a[MAXN],root[MAXN];
map<int,int> pos;
void Init()
{
cnt=0;
T[cnt].l=0;T[cnt].r=0;T[cnt].sum=0;
root[cnt]=0;
pos.clear();
}
void Update(int l,int r,int &now,int pre,int pos,int add)
{
T[++cnt]=T[pre];T[cnt].sum+=add;now=cnt;
if(l==r)
return;
int mid=(l+r)>>1;
if(pos<=mid)
Update(l,mid,T[cnt].l,T[pre].l,pos,add);
else
Update(mid+1,r,T[cnt].r,T[pre].r,pos,add);
}
int Query(int l,int r,int root,int left)
{
if(l>=left)
return T[root].sum;
int mid=(l+r)>>1;
if(mid>=left)
return Query(l,mid,T[root].l,left)+T[T[root].r].sum;
else
return Query(mid+1,r,T[root].r,left);
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
Init();
int temp;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(!pos.count(a[i]))//直接加
{
Update(1,n,root[i],root[i-1],i,1);
}
else
{
Update(1,n,temp,root[i-1],pos[a[i]],-1);
Update(1,n,root[i],temp,i,1);
}
pos[a[i]]=i;
}
int q,l,r;
scanf("%d",&q);
for(int i=0;i<q;++i)
{
scanf("%d%d",&l,&r);
printf("%d\n",Query(1,n,root[r],l));
}
}
return 0;
}
2.離線~樹狀陣列(線段樹也行)
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <cstring>
using namespace std;
const int maxn=30000+5;
map<int,int> mp;
int data[maxn],a[maxn],ans[200000+5];
struct node{
int l,r,id;
bool operator<(node t)const{
return r<t.r;
}
}q[200000+5];
int lowbit(int x)
{
return x&-x;
}
int Get_sum(int i)
{
int ans=0;
while(i>0){
ans+=data[i];
i-=lowbit(i);
}
return ans;
}
void Update(int i,int x)
{
while(i<maxn){
data[i]+=x;
i+=lowbit(i);
}
}
int main()
{
int n;
while(~scanf("%d",&n))
{
memset(data,0,sizeof(data));
mp.clear();
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int m;
scanf("%d",&m);
for(int i=0;i<m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q,q+m);
int pre=1;
for(int i=0;i<m;i++)
{
for(int j=pre;j<=q[i].r;j++)
{
if(mp[a[j]]!=0)
Update(mp[a[j]],-1);
Update(j,1);
mp[a[j]]=j;
}
pre=q[i].r+1;
ans[q[i].id]=Get_sum(q[i].r)-Get_sum(q[i].l-1);
}
for(int i=0;i<m;i++)
printf("%d\n",ans[i]);
}
return 0;
}