P4887 【模板】莫队二次离线(第十四分块(前体))
题意
分析
莫队二次离线模板题。
首先观察发现,二进制中有 \(k\) 个 1 的这样的数在这道题范围中只有 \(C_{14}^{7}\) 大约 3000 个数。
然后我们就可以直接枚举一下把这些数都保存下来。
接下来我们考虑怎么做,这个区间限制显然可以莫队,但是直接做的话相当于我们每次都可以利用 \(x \bigoplus y=z \to x\bigoplus z=y\) 这个性质,每次查询 \(x\) 对于所有 3000 多个数异或结果的桶的个数。
这样做我们莫队的转移复杂度将无法承受,是 \(O(C_{14}^{7}\times n\sqrt{n})\) 的。
那么二离莫队正是用于普通莫队转移复杂度我们无法接受,但是信息可以离线预处理的时候的一种做法。
“二次离线”的名字也是这样来的,就是说,我们这里本身莫队就是一个离线操作了,现在我们再次把这个莫队移动端点这个过程作为询问来离线计算贡献,也就是二次离线。
这里的话,我们就可以把每次询问这样来处理:(这里只讨论移动 \(r\) 指针到 \(r`\) \((r\leq r`)\),其他的类似。)
我们可以把这个询问拆成前缀和再差分的形式,也就是 \([1,r]\) 中和 \(x\) 能匹配的个数减去 \([1,l-1]\) 中和 \(x\) 能匹配的个数。
那么一次移动就是询问 \([1,r]\)对\(r+1\)的贡献,和\([1,l]\)对\(r+1\)的贡献。
而从 \(r\to r`\)就是 \([1,i-1]\)对\(i\)的贡献之和,其中\(i\in [r+1,r`]\),以及 \([1,l-1]\) 对 \([r+1,r`]\)的贡献之和。
我们发现,前者一直是一个前缀的形式,那么我们可以直接预处理出来,而后者是一个“定前缀”和一个区间的询问,我们由莫队复杂度分析可以得到后面的那一个区间的所有长度之和在 \(n\sqrt{n}\) 级别,可以直接把这个询问离线下来,挂到 \(l-1\) 处快速处理。
那么这里问题得到解决,值得一提的是,后面那一部分的总状态数通常都是 \(n\sqrt{n}\) ,这也几乎直接要求我们必须使用查询很快的算法,但是我们发现这一部分的修改是 \(O(n)\) 级别的,我们可以做到最坏\(\sqrt{n}\)的单次插入,以此来保证询问的复杂度(比如值域分块然后维护前缀和标记)。
代码
#include <bits/stdc++.h>
using namespace std;
template <typename T>
inline void read(T &x){
x=0;char ch=getchar();bool f=false;
while(!isdigit(ch)){if(ch=='-'){f=true;}ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return ;
}
template <typename T>
inline void write(T x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10^48);
return ;
}
const int N=1e5+5,V=(1<<14);
#define ll long long
int n,m,k,a[N],block,siz,bl[N];
ll pre[N],suf[N],Ans[N];
int cnt[V+5];
struct Query{
int l,r,id;
inline bool operator <(const Query &B) const{return (bl[l]^bl[B.l])?l<B.l:(bl[l]&1?r<B.r:r>B.r);}
}Q[N];
struct Que{int l,r,id,op;};
vector<Que> vec1[N],vec2[N];
vector<int> v;
int main(){
read(n),read(m),read(k);
block=sqrt(n);
for(int i=1;i<=n;i++) read(a[i]),bl[i]=(i-1)/block+1;
for(int i=0;i<V;i++) if(__builtin_popcount(i)==k) v.push_back(i);
for(int i=1;i<=m;i++) read(Q[i].l),read(Q[i].r),Q[i].id=i;
for(int i=1;i<=n;i++){
pre[i]=pre[i-1];
for(int j:v) pre[i]+=cnt[j^a[i]];
cnt[a[i]]++;
}
memset(cnt,0,sizeof(cnt));
for(int i=n;i>=1;i--){
suf[i]=suf[i+1];
for(int j:v) suf[i]+=cnt[j^a[i]];
cnt[a[i]]++;
}
sort(Q+1,Q+m+1);
int l=1,r=0;
for(int i=1;i<=m;i++){
Ans[Q[i].id]=pre[Q[i].r]-pre[r]+suf[Q[i].l]-suf[l];
if(l<Q[i].l) vec2[r+1].push_back({l,Q[i].l-1,Q[i].id,1});
if(l>Q[i].l) vec2[r+1].push_back({Q[i].l,l-1,Q[i].id,-1});
l=Q[i].l;
if(r<Q[i].r) vec1[l-1].push_back({r+1,Q[i].r,Q[i].id,-1});
if(r>Q[i].r) vec1[l-1].push_back({Q[i].r+1,r,Q[i].id,1});
r=Q[i].r;
}
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++){
for(int j:v) cnt[a[i]^j]++;
const int len=vec1[i].size();
for(int j=0;j<len;j++){
const int l=vec1[i][j].l,r=vec1[i][j].r,id=vec1[i][j].id;ll sum=0;
for(int k=l;k<=r;k++) sum+=cnt[a[k]];
Ans[id]+=sum*vec1[i][j].op;
}
}
memset(cnt,0,sizeof(cnt));
for(int i=n;i>=1;i--){
for(int j:v) cnt[a[i]^j]++;
const int len=vec2[i].size();
for(int j=0;j<len;j++){
const int l=vec2[i][j].l,r=vec2[i][j].r,id=vec2[i][j].id;ll sum=0;
for(int k=l;k<=r;k++) sum+=cnt[a[k]];
Ans[id]+=sum*vec2[i][j].op;
}
}
for(int i=2;i<=m;i++) Ans[Q[i].id]+=Ans[Q[i-1].id];
for(int i=1;i<=m;i++) write(Ans[i]),putchar('\n');
return 0;
}