wmz的数数(数状数组)
wmz的数数(数状数组)
题目描述
\(wmz\)从小就显现出了过人的天赋,他出生的第三天就证明了哥德巴赫猜想,第五天就证明了质能方程,出生一星期之后,他觉得\(P\)是否等于\(NP\)这个问题比前面他证明的这些定理好玩多了,于是他成为了一名计算机科学家。
在他开始接触计算机科学的第一天,他就已经刷遍了所有\(oj\),这为他今后建立王国推行全民刷题计划打下了坚实的基础。在他接触计算机科学一星期之后,他就已经通读了所有顶级会议的\(paper\),并且在多方面同时出了许多成果。
他对他王国的国民智商有非常高的要求,他为想加入他的王国国籍的人准备了很多试题去测试智商,但因为他接触计算机科学第一天学到的东西就已经超过正常人一生的水平了,所以他出的试题水平在他接触计算机科学\(1\)分钟以内的水平。
-------------------------------------------吹牛分割线------------------------------------------------
\(wmz\)拥有一个长度为\(n\)的数字序列,他向你询问了\(q\)个问题,每个问题都是告诉你两个数\(L\)和\(R\),问你在序列的第\(L\)到第\(R\)个数的这一段序列中,有多少个数字\(k\),满足在这一段中恰好出现了\(k\)次。
输入格式
输入的第一行为两个整数\(n\),\(q\),表示序列的长度和问题的个数。
接下来一行有\(n\)个整数,表示\(wmz\)的数字序列。
接下来\(q\)行,每行有两个整数\(L\),\(R\),其含义如题目中所示。
输出格式
输出共\(q\)行,每行有一个整数,表示第\(i\)个问题的答案。
样例
样例输入
7 2
3 1 2 2 3 3 7
1 7
3 4
样例输出
3
1
数据范围与提示
对于\(30\%\)的数据,保证\(1 \leq n,q \leq 100\);
对于\(70\%\)的数据,保证\(1 \leq n,q \leq 10^5\);
对于\(100\%\)的数据,保证\(1 \leq n,q \leq 10^6\),序列中的每个数都在\([1,n]\)范围内
分析
用莫队离线处理可以拿到\(70\)分
然而对于\(10^6\)的数据显然会超时
\(100\)分的做法要维护一个数组\(t[i]\),代表如果选择\(i\)这个位置上的数,答案将会增加\(t[i]\)
对于一个区间来说,如果在区间中有某一个数\(i\),它在位置\(x\)上恰好是第\(i\)次出现,那么我们需要将\(t[x]\)加一
如果它在位置\(y\)上恰好是第\(i+1\)次出现,那么我们需要将\(t[y]\)减一
每次询问的答案就是询问区间中\(t\)数组的和
因为对于左端点不同的区间,某一个数\(i\)第几次出现的位置是不同的
因此我们将左端点从大到小排序,用一个代表左端点的指针向左扫
每次扫到一个值时,我们要先把上一次对这个值的操作撤销,然后看它是否满足加一或者减一的条件,对其进行新的操作
区间求和用数状数组维护即可
代码
#include<cstdio>
#include<vector>
#include<algorithm>
const int maxn=1e6+5;
std::vector<int>g[maxn];
inline int read(){
int x=0,fh=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
int n,q,a[maxn],ans[maxn],tr[maxn],sygz[maxn],sygy[maxn];
struct jl{
int l,r,id;
}b[maxn];
bool cmp(jl aa,jl bb){
return aa.l>bb.l;
}
int ef(int val,int wz){
int l=1,r=g[val].size(),mids;
while(l<=r){
mids=(l+r)>>1;
if(g[val][mids-1]>wz) r=mids-1;
else if(g[val][mids-1]<wz) l=mids+1;
else return mids;
}
return 0;
}//二分查找当前数是第几次出现
int lb(int xx){
return xx&-xx;
}
void ad(int wz,int val){
for(int i=wz;i<maxn;i+=lb(i)){
tr[i]+=val;
}
}
int qh(int wz){
int anss=0;
for(int i=wz;i>0;i-=lb(i)){
anss+=tr[i];
}
return anss;
}
int main(){
n=read(),q=read();
for(int i=1;i<=n;i++){
a[i]=read();
g[a[i]].push_back(i);
}
for(int i=1;i<=q;i++){
b[i].l=read(),b[i].r=read();
b[i].id=i;
}
std::sort(b+1,b+1+q,cmp);
int latl=n+1;//维护指针
for(int i=1;i<=q;i++){
while(latl>b[i].l){
latl--;
int wz=ef(a[latl],latl);
int tail=wz+a[latl]-1;
if(sygz[a[latl]]!=0) ad(sygz[a[latl]],-1);
if(sygy[a[latl]]!=0) ad(sygy[a[latl]],1);
//撤销上一次操作
if(tail>g[a[latl]].size()) continue;
else if(tail==g[a[latl]].size()){
ad(g[a[latl]][tail-1],1);
sygz[a[latl]]=g[a[latl]][tail-1];
sygy[a[latl]]=0;
}
else {
ad(g[a[latl]][tail-1],1);
ad(g[a[latl]][tail],-1);
sygz[a[latl]]=g[a[latl]][tail-1];
sygy[a[latl]]=g[a[latl]][tail];
}
}
ans[b[i].id]=qh(b[i].r)-qh(b[i].l-1);
}
for(int i=1;i<=q;i++){
printf("%d\n",ans[i]);
}
return 0;
}