莫队的思想
对于区间查询数字出现次数的问题,我们可以使用莫队。
比如,当询问区间是
1
1
1~
n
n
n时,可以通过
O
(
N
)
O(N)
O(N)的复杂度解决。
那接下来再询问
2
2
2~
n
n
n或者
1
1
1~
n
n
n
−
-
−
1
1
1时,就可以通过
O
(
1
)
O(1)
O(1)的复杂度解决这个询问。
所以,当有m个询问的时候,我们就可以根据第
i
i
i个询问的结果推出第
i
i
i
+
+
+
1
1
1个询问的结果。这样就不用每一次询问都用
O
(
N
)
O(N)
O(N)去跑了。
但是这可以被卡:
1 1
n n
1 1
n n
……
于是就产生了优化版:对左端点进行排序。
但是这依旧可以被卡:
1 n
2 1
3 n
4 1
……
所以我们就可以对左端点进行分块(后面发文章讲)
2025年3月7日签到:分块文章已发布—>链接
经过精密的计算,可以得出每个块大小设为
n
\sqrt{n}
n时,可以使时间复杂度最优。
说多了也没用,来看道例题:
例题
1.小B的询问(模版题)
题目传送门
莫队采用的是离线操作。
所以我们先用一个数组
q
q
q记录询问的区间。
接着,我们对
q
q
q数组按一下规则进行排序。
bool cmp(node a,node b){
return (a.l/s)<(b.l/s)||(a.l/s==b.l/s&&a.r<b.r);
}
先按照块排序,在同一块内则按右端点排序。
接着来看计算的地方
(
x
+
1
)
2
=
x
2
+
2
x
+
1
(x+1)^2=x^2+2x+1
(x+1)2=x2+2x+1,这是一个众所周知的等式。
所以,当
c
i
c_i
ci的值增加
1
1
1时,总数应该增加
2
∗
c
i
+
1
2*c_i+1
2∗ci+1
同理,当
c
i
c_i
ci的值减少
1
1
1时,总数应该减少
2
∗
c
i
−
1
2*c_i-1
2∗ci−1(注意和增加时不同)
我们用数组
b
b
b记录元素出现的次数,那么……
增加
void add(int x){
ans+=2*(b[x]++)+1;
}
减少
void del(int x){
ans-=2*(b[x]--)-1;
}
接着就是从当前询问转到下一个询问的事情:
//上一个区间比当前区间大,缩小区间范围
while(l1<f[i].l)del(a[l1++]);
while(r1>f[i].r)del(a[r1--]);
//上一个区间比当前区间小,扩大区间范围
while(l1>f[i].l)add(a[--l1]);
while(r1<f[i].r)add(a[++r1]);
answer[f[i].id]=ans;//存储答案,注意不是answer[i]
到这里,核心代码就完了
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,k;
int a[N];
struct node{
int l,r,id;
}f[N];
int b[N];
int s;
bool cmp(node a,node b){
return (a.l/s)<(b.l/s)||(a.l/s==b.l/s&&a.r<b.r);
}
int ans;
void add(int x){
ans+=2*(b[x]++)+1;
}
void del(int x){
ans-=2*(b[x]--)-1;
}
int answer[N];
signed main(){
ios::sync_with_stdio(0);
cin>>n>>m>>k;
for(int i=1;i<=n;i++)cin>>a[i];
s=sqrt(n);
for(int i=1;i<=m;i++){
cin>>f[i].l>>f[i].r;
f[i].id=i;
}
sort(f+1,f+1+m,cmp);
int l1=1,r1=0;
for(int i=1;i<=m;i++){
while(l1<f[i].l)del(a[l1++]);
while(r1>f[i].r)del(a[r1--]);
while(l1>f[i].l)add(a[--l1]);
while(r1<f[i].r)add(a[++r1]);
answer[f[i].id]=ans;
}
for(int i=1;i<=m;i++)cout<<answer[i]<<'\n';
}
515

被折叠的 条评论
为什么被折叠?



