题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1878
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 6Sample Output
2
2
4HINT
对于20%的数据,N ≤ 100,M ≤ 1000;
对于40%的数据,N ≤ 3000,M ≤ 200000;
对于100%的数据,N ≤ 50000,M ≤ 200000。
题解:
首先树状数组维护一段区间内的数的种类数。
然后就是在线转离线的经典做法,因为这里面没有涉及修改的,所有我们可以这样做,按照右端点从小到大对询问排序。
之后从左到右枚举 i ,同时把上次出现该数的位置的影响减去,加上当前数的影响。
具体看一个例子(来自HH的项链)
当now_r=1,insert(1,1)表示在位置1出现了一个未重复的数,需要在树状数组上对应的位置+1,此时树状数组对应序列每个位置上的值为:1 0 0 0
当now_r=2,insert(2,1):1 1 0 0
当now_r=1,由于数a[3](就是1)之前出现过,最后一遍出现的地方为1,则insert(1,-1),insert(3,1),即减去之前位置加上的种类数转而在右边的位置+1,:0 1 1 0
当now_r=3,insert(4,1):0 1 1 1
这样,每次i更新时看是否i=一个r,while输出sum[l,r]=sum[r]-sum[l-1](询问[2,3]=sum[3]-sum[2-1]=2)
最后一点,为了记录每个数最后一遍出现过的位置,需要一个last数组。last[i]=0表示数i还未出现过,last[i]=k表示数i最后一次出现的位置为k。
详解请参考代码:
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
#define PI atan(1.0)*4
#define e 2.718281828
#define rp(i,s,t) for (i = (s); i <= (t); i++)
#define RP(i,s,t) for (i = (t); i >= (s); i--)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define push_back() pb()
#define fastIn \
ios_base::sync_with_stdio(0); \
cin.tie(0);
using namespace std;
inline int read()
{
int a=0,b=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
b=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
a=(a<<3)+(a<<1)+c-'0';
c=getchar();
}
return a*b;
}
inline void write(int n)
{
if(n<0)
{
putchar('-');
n=-n;
}
if(n>=10)
write(n/10);
putchar(n%10+'0');
}
const int N=1e6+7;
int n;
int a[N],ans[N],last[N],sum[N];
//a数组用来读入数据,ans存储查询结果
//last[a[i]]表示上一次a[i]出现的下标,不出现为0
//sum即维护的树状数组,表示一段区间内的种类数
struct node{
int l,r,num;
}p[N];//存储每次查询的左右端点,以及查询的下标
int lowbit(int x){
return x&(-x);
}
void update(int i,int x){//更新
while(i<=n){
sum[i]+=x;
i+=lowbit(i);
}
}
int query(int i){//查询
int res=0;
while(i>=1){
res+=sum[i];
i-=lowbit(i);
}
return res;
}
bool cmp(node p1,node p2){//按照查询的右端点排序
return p1.r<p2.r;
}
//在线转离线的经典例题
int main(){
n=read();
int i;
rp(i,1,n) a[i]=read();
int m=read();
rp(i,1,m) p[i].l=read(),p[i].r=read(),p[i].num=i;
sort(p+1,p+1+m,cmp);//排序
int nowqueues=1;
//从左到右一个一个推和查询相同的r
rp(i,1,n){
update(i,1);//默认为位置上出现未重复的数
//如果当前位置的数出现过,则之前出现的位置减去
//保证每次查询的时候都是不重复的数
if(last[a[i]]) update(last[a[i]],-1);
last[a[i]]=i;//更新a[i]最后一次出现的位置
while(p[nowqueues].r==i&&nowqueues<=m){//如果和查询的右区间相同
ans[p[nowqueues].num]=query(p[nowqueues].r)-query(p[nowqueues].l-1);
//答案即为([1,r]-[1,l-1]),满足可减性,为什么呢?
//因为我们已经把重复的都给去掉了。
nowqueues++;
}
}
rp(i,1,m)
printf("%d\n",ans[i]);
return 0;
}