[SDOI2009]HH的項鏈
題目描述
HH 有一串由各種漂亮的貝殼組成的項鏈。HH 相信不同的貝殼會帶來好運,所以每次散步完後,他都會隨意取出一段貝殼,思考它們所表達的含義。HH 不斷地收集新的貝殼,因此,他的項鏈變得越來越長。有一天,他突然提出了一個問題:某一段貝殼中,包含了多少種不同的貝殼?這個問題很難回答……因為項鏈實在是太長了。於是,他只好求助睿智的你,來解決這個問題。
輸入輸出格式
輸入格式:
第一行:一個整數N,表示項鏈的長度。
第二行:N 個整數,表示依次表示項鏈中貝殼的編號(編號為0 到1000000 之間的整數)。
第三行:一個整數M,表示HH 詢問的個數。
接下來M 行:每行兩個整數,L 和R(1 ≤ L ≤ R ≤ N),表示詢問的區間。
輸出格式:
M 行,每行一個整數,依次表示詢問對應的答案。
輸入輸出樣例
輸入樣例#1:6 1 2 3 4 3 5 3 1 2 3 5 2 6輸出樣例#1:
2 2 4
說明
數據範圍:
對於100%的數據,N <= 50000,M <= 200000。
本題有兩種方法:莫隊和樹狀數組
莫隊:
核心代碼:
1 void add ( int pos ) { 2 ++cnt[a[pos]] ; 3 if ( cnt[a[pos]] == 1 ) 4 ++ answer ; 5 } 6 void remove ( int pos ) {7 -- cnt[a[pos]] ; 8 if ( cnt[a[pos]] == 0 ) 9 -- answer ; 10 } 11 void solve() { 12 int curL = 1, curR = 0 ; // current L R 13 for ( each query [L,R] ) { 14 while ( curL < L ) 15 remove ( curL++ ) ; 16 while ( curL > L )17 add ( --curL ) ; 18 while ( curR < R ) 19 add ( ++curR ) ; 20 while ( curR > R ) 21 remove ( curR-- ) ; 22 cout << answer << endl ; 23 // Warning : please notice the order "--","++" and "cur" ; 24 } 25 }
復雜度為N^2,要減少curL和curR指針的移動次數
我們可以通過離線下所有的詢問,然後通過某種排序,讓兩個指針跑動的距離盡量變少。具體的做法是把N劃分成√N段,每段長度都是√N,然後在把所有詢問按照L端點排序,看各個詢問被劃分到哪一塊裏。接著,對於各個劃分出的段,在各自的段裏,將它包含的所有區間再按照R端點排序。
復雜度為O(N*√N)
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<cmath> 6 using namespace std; 7 struct Node 8 { 9 int l,r,num; 10 }s[200001]; 11 int tim,a[50001],ans[200001],n,m,cnt[1000001],answer; 12 bool cmp(Node a,Node b) 13 { 14 return ((a.l/tim)==(b.l/tim)?a.r<b.r:a.l<b.l); 15 } 16 void add(int x) 17 { 18 if (++cnt[a[x]]==1) answer++; 19 } 20 void remove(int x) 21 { 22 if ((--cnt[a[x]])==0) answer--; 23 } 24 int main() 25 {int i,j,l,r; 26 cin>>n; 27 for (i=1;i<=n;i++) 28 { 29 scanf("%d",&a[i]); 30 } 31 cin>>m;tim=sqrt(m); 32 for (i=1;i<=m;i++) 33 { 34 scanf("%d%d",&s[i].l,&s[i].r); 35 s[i].num=i; 36 } 37 sort(s+1,s+m+1,cmp); 38 l=1;r=0; 39 for (i=1;i<=m;i++) 40 { 41 while (l<s[i].l) 42 remove(l++); 43 while (l>s[i].l) 44 add(--l); 45 while (r<s[i].r) 46 add(++r); 47 while (r>s[i].r) 48 remove(r--); 49 ans[s[i].num]=answer; 50 } 51 for (i=1;i<=m;i++) 52 printf("%d\n",ans[i]); 53 }
法2:樹狀數組:
可以想到用樹狀數組維護區間答案,但明顯,ans[i]!=sum(r)-sum(l-1);
此題首先應考慮到這樣一個結論:
對於若幹個詢問的區間[l,r],如果他們的r都相等的話,那麽項鏈中出現的同一個數字,一定是只關心出現在最右邊的那一個的,例如:
項鏈是:1 3 4 5 1
那麽,對於r=5的所有的詢問來說,第一個位置上的1完全沒有意義,因為r已經在第五個1的右邊,對於任何查詢的[L,5]區間來說,如果第一個1被算了,那麽他完全可以用第五個1來替代。
因此,我們可以對所有查詢的區間按照r來排序,然後再來維護一個樹狀數組,這個樹狀數組是用來幹什麽的呢?看下面的例子:
1 2 1 3
對於第一個1,insert(1,1);表示第一個位置出現了一個不一樣的數字,此時樹狀數組所表示的每個位置上的數字(不是它本身的值而是它對應的每個位置上的數字)是:1 0 0 0
對於第二個2,insert(2,1);此時樹狀數組表示的每個數字是1 1 0 0
對於第三個1,因為之前出現過1了,因此首先把那個1所在的位置刪掉insert(1,-1),然後在把它加進來insert(3,1)。此時每個數字是0 1 1 0
如果此時有一個詢問[2,3],那麽直接求sum(3)-sum(2-1)=2就是答案。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 struct Node 7 { 8 int l,r,num; 9 }s[200001]; 10 int a[50001],ans[200001],n,m,c[100001],vis[1000001]; 11 bool cmp(Node a,Node b) 12 { 13 return (a.r<b.r||(a.r==b.r&&a.l<b.l)); 14 } 15 int getsum(int x) 16 { 17 int s=0; 18 while (x) 19 { 20 s+=c[x]; 21 x-=(x&(-x)); 22 } 23 return s; 24 } 25 void add(int x,int d) 26 { 27 while (x<=n) 28 { 29 c[x]+=d; 30 x+=(x&(-x)); 31 } 32 } 33 int main() 34 {int i,j; 35 cin>>n; 36 for (i=1;i<=n;i++) 37 { 38 scanf("%d",&a[i]); 39 } 40 cin>>m; 41 for (i=1;i<=m;i++) 42 { 43 scanf("%d%d",&s[i].l,&s[i].r); 44 s[i].num=i; 45 } 46 sort(s+1,s+m+1,cmp); 47 j=1; 48 for (i=1;i<=n+1;i++) 49 { 50 while (j<=m&&i>s[j].r) 51 { 52 ans[s[j].num]=getsum(s[j].r)-getsum(s[j].l-1); 53 j++; 54 } 55 if (i>n) break; 56 if (vis[a[i]]) 57 { 58 add(vis[a[i]],-1); 59 vis[a[i]]=i; 60 add(i,1); 61 } 62 else 63 { 64 vis[a[i]]=i; 65 add(i,1); 66 } 67 } 68 for (i=1;i<=m;i++) 69 printf("%d\n",ans[i]); 70 }
[SDOI2009]HH的項鏈