1. 程式人生 > >[SDOI2009]HH的項鏈

[SDOI2009]HH的項鏈

tro style truct 離線 組成 tdi 區間 scan ==

題目描述

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的項鏈