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;
}
标签:return,int,sum,枚举,HH,项链,区间,void
From: https://www.cnblogs.com/Hanggoash/p/18416533