[SDOI2009]HH的项链
题目描述
HH 有一串由各种漂亮的贝壳组成的项链。HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。HH 不断地收集新的贝壳,因此,他的项链变得越来越长。有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?这个问题很难回答……因为项链实在是太长了。于是,他只好求助睿智的你,来解决这个问题。
输入输出格式
输入格式:
第一行:一个整数N,表示项链的长度。
第二行:N 个整数,表示依次表示项链中贝壳的编号(编号为0 到1000000 之间的整数)。
第三行:一个整数M,表示HH 询问的个数。
接下来M 行:每行两个整数,L 和R(1 ≤ L ≤ R ≤ N),表示询问的区间。
输出格式:
M 行,每行一个整数,依次表示询问对应的答案。
输入输出样例
6 1 2 3 4 3 5 3 1 2 3 5 2 6
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 }