莫队二次离线
前言
众所周知,莫队是一种可爱的离线算法,它的时间复杂度为
适用范围:
- 一个数对区间的贡献与区间内的数有关;
- 设
F(x,[l,r]) 为x 对区间[l,r] 的贡献,其满足性质F(x,[l,r])=F(x,[1,r])-F(x,[1,l-1]) 。
实现
考虑在挪指针时对答案产生了哪些贡献,下文的
在右指针移动到
在左指针移动到
我们注意到,
莫队的指针移动是连续的,也就是说,在上面式子中的
for(int i=1,l=1,r=0;i<=m;i++){
if(l>q[i].l) v[r].emplace_back(q[i].l,l-1,i,1);
while(l>q[i].l) --l,q[i].ans-=p[l];
if(r<q[i].r) v[l-1].emplace_back(r+1,q[i].r,i,-1);
while(r<q[i].r) ++r,q[i].ans+=p[r];
if(l<q[i].l) v[r].emplace_back(l,q[i].l-1,i,-1);
while(l<q[i].l) q[i].ans+=p[l],++l;
if(r>q[i].r) v[l-1].emplace_back(q[i].r+1,r,i,1);
while(r>q[i].r) q[i].ans-=p[r],--r;
}
这样还有最后一个问题,我们会发现一个指针在询问
UPD: 这里给出一个常数优化,p 数组可以用前缀和优化
for(int i=1,l=1,r=0;i<=m;i++)
if(l>q[i].l) v[r].emplace_back(q[i].l,l-1,q[i].id,1),ans[q[i].id]-=p[l-1]-p[q[i].l-1],l=q[i].l;
if(r<q[i].r) v[l-1].emplace_back(r+1,q[i].r,q[i].id,-1),ans[q[i].id]+=p[q[i].r]-p[r],r=q[i].r;
if(l<q[i].l) v[r].emplace_back(l,q[i].l-1,q[i].id,-1),ans[q[i].id]+=p[q[i].l-1]-p[l-1],l=q[i].l;
if(r>q[i].r) v[l-1].emplace_back(q[i].r+1,r,q[i].id,1),ans[q[i].id]-=p[r]-p[q[i].r],r=q[i].r;
}
关于本题
我们预处理出所有有
代码
这里给出 P4887 的代码:
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<cstring>
#include<tuple>
int n,m,k,bel[100010],sz,a[100010],t[100010],p[100010];
struct query{
int l,r,id;
long long ans;
bool operator <(query const &x)const{
return bel[l]==bel[x.l]?r<x.r:l<x.l;
}
}q[100010];
std::vector<int> b;
std::vector<std::tuple<int,int,int,int>>v[100010];
long long ans[100010];
int bitcount(unsigned x){
int ans(0);
while(x)x-=x&-x,++ans;
return ans;
}
int main(){
scanf("%d%d%d",&n,&m,&k);
if(k>14){
for(int i=1;i<=m;i++)puts("0");
return 0;
}
sz=n/sqrt(m);//莫队的正确块长,比sqrt(n)要快很多
for(int i=1;i<=n;i++)scanf("%d",a+i),bel[i]=(i-1)/sz+1;
for(int i=1;i<=m;i++)scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
for(int i=0;i<16384;i++)
if(bitcount(i)==k)
b.push_back(i);
std::sort(q+1,q+m+1);
for(int i=1;i<=n;i++){//预处理
p[i]=t[a[i]];
for(const auto& x:b)++t[a[i]^x];
}
memset(t,0,sizeof(t));
for(int i=1,l=1,r=0;i<=m;i++){//莫队
if(l>q[i].l) v[r].emplace_back(q[i].l,l-1,i,1);
while(l>q[i].l) --l,q[i].ans-=p[l];
if(r<q[i].r) v[l-1].emplace_back(r+1,q[i].r,i,-1);
while(r<q[i].r) ++r,q[i].ans+=p[r];
if(l<q[i].l) v[r].emplace_back(l,q[i].l-1,i,-1);
while(l<q[i].l) q[i].ans+=p[l],++l;
if(r>q[i].r) v[l-1].emplace_back(q[i].r+1,r,i,1);
while(r>q[i].r) q[i].ans-=p[r],--r;
}
for(int i=1;i<=n;i++){//扫描线
for(const auto& x:b)++t[a[i]^x];
for(const auto& x:v[i]){
for(int j=std::get<0>(x);j<=std::get<1>(x);j++){
if(j<=i&&k==0) q[std::get<2>(x)].ans+=std::get<3>(x)*(t[a[j]]-1);
else q[std::get<2>(x)].ans+=std::get<3>(x)*t[a[j]];
}
}
}
for(int i=1;i<=m;i++)q[i].ans+=q[i-1].ans;//前缀和
for(int i=1;i<=m;i++)ans[q[i].id]=q[i].ans;
for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
return 0;
}
习题
P4887
P5047
P5501