[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 }

 

 

 

 

posted @ 2017-07-15 18:02  Z-Y-Y-S  阅读(334)  评论(0编辑  收藏  举报