題目大意:詢問一個序列中區間[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;
}