首页 > 代码库 > BZOJ1878: [SDOI2009]HH的项链

BZOJ1878: [SDOI2009]HH的项链

1878: [SDOI2009]HH的项链

Time Limit: 4 Sec  Memory Limit: 64 MB
Submit: 1673  Solved: 808
[Submit][Status]

Description

HH有一串由各种漂亮的贝壳组成的项链。HH相信不同的贝壳会带来好运,所以每次散步 完后,他都会随意取出一段贝壳,思考它们所表达的含义。HH不断地收集新的贝壳,因此, 他的项链变得越来越长。有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同 的贝壳?这个问题很难回答。。。因为项链实在是太长了。于是,他只好求助睿智的你,来解 决这个问题。

Input

第一行:一个整数N,表示项链的长度。 第二行:N个整数,表示依次表示项链中贝壳的编号(编号为0到1000000之间的整数)。 第三行:一个整数M,表示HH询问的个数。 接下来M行:每行两个整数,L和R(1 ≤ L ≤ R ≤ N),表示询问的区间。

Output

M行,每行一个整数,依次表示询问对应的答案。

Sample Input

6
1 2 3 4 3 5
3
1 2
3 5
2 6

Sample Output

2
2
4

HINT


对于20%的数据,N ≤ 100,M ≤ 1000;
对于40%的数据,N ≤ 3000,M ≤ 200000;
对于100%的数据,N ≤ 50000,M ≤ 200000。

Source

Day2

题解:
这题以前做树状数组的时候做过,只不过现在早把思想忘了,今天再来做一下。
巧妙的运用了前缀和,使得树状数组发挥作用。
这题树状数组有两种写法,具体见代码:
1.按右端点排序
 1 #include<cstdio> 2 #include<cstdlib> 3 #include<cmath> 4 #include<cstring> 5 #include<algorithm> 6 #include<iostream> 7 #include<vector> 8 #include<map> 9 #include<set>10 #include<queue>11 #define inf 100000000012 #define maxn 50000+100013 #define maxm 200000+1000014 #define maxk 1000000+100015 #define eps 1e-1016 #define ll long long17 #define pa pair<int,int>18 using namespace std;19 inline int read()20 {21     int x=0,f=1;char ch=getchar();22     while(ch<0||ch>9){if(ch==-)f=-1;ch=getchar();}23     while(ch>=0&&ch<=9){x=10*x+ch-0;ch=getchar();}24     return x*f;25 }26 struct rec{int x,y,id;}a[maxm];27 int s[maxn],ans[maxm],next[maxn],head[maxk],n,m;28 void add(int x,int y)29 {30     for(;x<=n;x+=x&(-x))s[x]+=y;31 }32 int sum(int x)33 {34     int t=0;35     for(;x;x-=x&(-x))t+=s[x];36     return t;37 }38 bool cmp(rec a,rec b)39 {40     return a.y<b.y;41 }42 int main()43 {44     freopen("input.txt","r",stdin);45     freopen("output.txt","w",stdout);46     n=read();47     for(int i=1;i<=n;i++)48      {49          int x=read();50          next[i]=head[x];51          head[x]=i;52      }53     m=read();54     for(int i=1;i<=m;i++)55      a[i].x=read(),a[i].y=read(),a[i].id=i;56     sort(a+1,a+m+1,cmp);57     int now=0;58     for(int i=1;i<=m;i++)59     {60         while(now<a[i].y)61         {62             now++;63             add(next[now]+1,1);64             add(now+1,-1);65         }66         ans[a[i].id]=sum(a[i].x);67     }  68     for(int i=1;i<=m;i++)printf("%d\n",ans[i]);69     return 0;70 }
View Code

2.按左端点排序

 1 #include<cstdio> 2 #include<cstdlib> 3 #include<cmath> 4 #include<cstring> 5 #include<algorithm> 6 #include<iostream> 7 #include<vector> 8 #include<map> 9 #include<set>10 #include<queue>11 #define inf 100000000012 #define maxn 50000+100013 #define maxm 200000+1000014 #define maxk 1000000+100015 #define eps 1e-1016 #define ll long long17 #define pa pair<int,int>18 using namespace std;19 inline int read()20 {21     int x=0,f=1;char ch=getchar();22     while(ch<0||ch>9){if(ch==-)f=-1;ch=getchar();}23     while(ch>=0&&ch<=9){x=10*x+ch-0;ch=getchar();}24     return x*f;25 }26 struct rec{int x,y,id;}a[maxm];27 int s[maxn],ans[maxm],next[maxn],head[maxk],n,m,mx,b[maxn];28 void add(int x,int y)29 {30     for(;x<=n;x+=x&(-x))s[x]+=y;31 }32 int sum(int x)33 {34     int t=0;35     for(;x;x-=x&(-x))t+=s[x];36     return t;37 }38 bool cmp(rec a,rec b)39 {40     return a.x==b.x?a.y<b.y:a.x<b.x;41 }42 int main()43 {44     freopen("input.txt","r",stdin);45     freopen("output.txt","w",stdout);46     n=read();47     for(int i=1;i<=n;i++)b[i]=read();48     for(int i=n;i>=1;i--)49      {50          next[i]=head[b[i]];51          head[b[i]]=i;52          mx=max(mx,b[i]);53      }54     m=read();55     for(int i=1;i<=m;i++)56      a[i].x=read(),a[i].y=read(),a[i].id=i;57     sort(a+1,a+m+1,cmp);58     for(int i=1;i<=mx;i++)if(head[i])add(head[i],1);59     int now=1;60     for(int i=1;i<=m;i++)61     {62         while(now<a[i].x)63         {64             if(next[now])add(next[now],1); 65             now++;66         }67         ans[a[i].id]=sum(a[i].y)-sum(a[i].x-1);68     }  69     for(int i=1;i<=m;i++)printf("%d\n",ans[i]);70     return 0;71 }
View Code

 

本题数据规模较大,需要合理使用数据结构。题目中有两个元素:区间和颜色。如果直接使用线段树套平衡树,需要牵扯到合并操作,时间复杂度很高(比如当询问类似[2,N-1]的时候)。因此,需要做一些预处理工作使得询问更容易解决。

观察在某一区间内出现的某种颜色,假设有若干个同色点都在这个区间中,而该颜色只能计算一遍,因此我们需要找出一个有代表性的点,当然是第一个最有代表性。观察该点,以及它前后同色点的位置,有什么可以利用的规律?很显然,它的上一个同色点肯定在区间左侧,而同区间内的该颜色的其他点,它们的上一个同色点显然在区间内。这样,我们的工作便是找到询问区间[l,r]中这样的点x的个数:点x的上一个同色点在询问区间[l,r]左侧。

到这一步,我们有一个在线算法:建线段树,将询问分成O(lbN)个小区间分别处理。如果再套平衡树,需要二分查找才能求符合条件的点的个数。这样总的理论最坏复杂度为O(M(lbN)3),虽然实际情况会好一些,但还是难以符合要求。我们分析一下该算法复杂度高的原因:平衡树难以很好支持求个数的运算,需要二分来实现。那么求个数效率最高的是什么数据结构?当然是树状数组。

当然,线段树是无法再套树状数组的,那样空间复杂度会达到O(N2),所以我们要舍弃线段树,寻找新的算法。对于一堆无序的询问区间,如果没有线段树,便很难处理。因此我们考虑将区间按左端点排序,使其有序,然后从左到右扫描每个点。再考虑那些上一个同色点在询问区间左侧的点,我们的目的是快速求出该区间中这种点的个数。而这些点的上一个同色点因为在询问区间左侧,所以已经被扫描过,而区间内其他点却没有!这样,如果我们换个角度,改上一个下一个,预处理出每个点i的下一个同色点的位置Next[i],并且在扫描过该点i后使树状数组C[Next[i]]=1。那么对于询问区间[l,r],答案便是Sumc[r]-Sumc[l-1]!这样,算法只有1”求和两种操作。问题到此得到圆满解决, 最终时间复杂度为O(MlbN)

BZOJ1878: [SDOI2009]HH的项链