题目大意:询问一个序列中区间[a,b]中不同的数有几个,无修改操作。
比较容易想到的是使用线段树套平衡树来解决,但是这道题需要有合并的操作,时间复杂度很高,不能接受。
我们可以考虑,当一个区间有若干个同色点时,我们只能算一个,所以我们需要找出一个具有代表性的点,于是我们可以想到找区间中某种颜色第一次出现的位置来代表。并且我们可以知道这样的点的共同特点为上一个该颜色的点在查询区间的左侧,这样问题转化为求区间[a,b]中上一个同色点在[a,b]左侧的点的数目。
于是可以使用线段树套一个线性表的方法做,二分查找,在log时间内求出小区间的答案,这样就可以做到满分了。
但是我们还可以考虑更优的算法,使用离线算法,将查询区间按照右界排序,然后从前到后处理。
具体来说就是:按区间右界排序,预处理出上一个同色点的位置,然后从前到后扫描,每次将上一个同色点的值加1,将当前位置下个位置的值减1,然后求当前区间的左界的前缀和就是答案了。
这样我们需要一个可以实现两种操作的数据结构:
1、将某个位置+1或-1
2、求前缀和
而实现这两种操作的最好的数据结构就是树状数组,所以我们就用一个树状数组维护就行了。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 200000 + 10;
const int maxm = 1000000 + 10;
struct ques
{
int l,r;
int pos;
}Seg[maxn];
int pre[maxn],col[maxn];
int sum[maxn],ans[maxn];
int last[maxm];
int n,m;
int cmp(const ques &a,const ques &b)
{
return a.r < b.r;
}
void init()
{
freopen("bzoj1878.in","r",stdin);
freopen("bzoj1878.out","w",stdout);
}
inline int lowbit(int x)
{
return x & -x;
}
void add(int x,int p)
{
while(x <= n)
{
sum[x] += p;
x += lowbit(x);
}
}
int getsum(int x)
{
int ret = 0;
while(x > 0)
{
ret += sum[x];
x -= lowbit(x);
}
return ret;
}
void readdata()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
scanf("%d",&col[i]);
pre[i] = last[col[i]];
last[col[i]] = i;
}
scanf("%d",&m);
for(int i = 1;i <= m;i++)
{
scanf("%d%d",&Seg[i].l,&Seg[i].r);
Seg[i].pos = i;
}
}
void solve()
{
stable_sort(Seg + 1,Seg + m + 1,cmp);
int now = 0;
for(int i = 1;i <= m;i++)
{
while(now < Seg[i].r)
{
++now;
add(pre[now] + 1,1);
if(now != n)add(now + 1,-1);
}
ans[Seg[i].pos] = getsum(Seg[i].l);
}
for(int i = 1;i <= m;i++)printf("%d\n",ans[i]);
}
int main()
{
init();
readdata();
solve();
return 0;
}