HH的项链 题解
题意
给定一个序列 \(a_N\) ,有 \(m\) 个询问 \([l,r]\) ,问在该区间中不同数的数量有多少。该题目可以和 ABC371E 对比着做。
思路
\(N\in[1,10^6]\) ,暴力枚举是 \(n^2\) 的会超时。
但是我们先假定这 \(n\) 个数字本就两两不同,那么他们各自都会产生 \(1\) 的贡献,如果每个数相应的位置都代表数值 \(1\) ,那么就是在查询 \(sum[r]-sum[l-1]\) 而已。
事实上上述假设并不能一定成立。
那么有一种和莫队和相似的巧妙算法就是先离线,然后再考虑区间之间移动时的增量。
核心
我们先按照右区间升序排序。
言下之意就是如果有 \(114514\) 个 \(3\) ,那你也只能算一种。我们保留刚刚的前缀和算法,钦定所有相同数字 \(x\) 所产生的贡献 \(1\) 都在当前最右边的 \(x\) 身上,其余的 \(x\) 贡献都为 \(0\),并且随着我们枚举区间不断更新这个"最右"的位置,这时候我们发现了按照右区间升序排序的优越性。
那就是下一个枚举到的区间要不就不包含 \(x\) 这个数字 ,要么就一定至少包含最右边的 \(x\) (可能同时包含左边的 \(x\) ),由于 \(r\) 是逐渐增大的,所以我们不可能枚举到一个包含左边 \(x\) 们,但又不包含最右边 \(x\) 的区间。因此我们就成功统计了 \(x\) 对当前区间 (如果有的话)的贡献。
所以对于一个 \([l,r]\) ,我们先做好从上一个 \(r_{las}\) 到这一个 \(r_{now}\) 的更新,再直接统计答案 \(sum[r]-sum[l-1]\) 就好了。
由于是单点修改并且多次查询,就选择树状数组了。
Code
#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void re(T &x)
{x=0;int f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}x*=f;
}
template<typename T>inline void wr(T x)
{if(x<0)putchar('-'),x=-x;if(x>9)wr(x/10);putchar(x%10^48);
}
const int N=1e6+5;
int v[N],c[N],n,m;
inline int lowbit(int x){return x&(-x);}
inline void add(int x,int v)
{for(;x<=n;x+=lowbit(x))c[x]+=v;
}
inline int query(int x){int ans=0;for(;x>=1;x-=lowbit(x))ans+=c[x];return ans;}
struct Q
{int l,r,idx;
}a[N];
int ans[N];
bool cmp(Q a,Q b){return a.r==b.r?a.l>b.l:a.r<b.r;}
inline void pre()
{re(n);for(register int i=1;i<=n;++i)re(v[i]);re(m);for(register int i=1;i<=m;++i){re(a[i].l),re(a[i].r);a[i].idx=i;}sort(a+1,a+m+1,cmp);
}
int las[N];
inline void solve()
{int st=1;for(int i=1;i<=m;++i){for(;st<=a[i].r;++st){if(las[v[st]])add(las[v[st]],-1);add(st,1); las[v[st]]=st;}ans[a[i].idx]=query(a[i].r)-query(a[i].l-1);}
}
int main()
{pre();solve();for(register int i=1;i<=m;++i)wr(ans[i]),putchar('\n');return 0;
}