【洛谷】P4887 【模板】莫队二次离线
题意
给定一个长度为 的序列和一个常数 ,每次询问一个区间 内,有多少对 ,满足 ,且 的二进制表示下恰好有 位为 。
数据范围
。
思路
按照普通莫队的思路,设当前的指针为 ,询问的区间为 , 表示 中有多少数与 配对。以 转移到 为例,更新的答案就为 。对于形如 ,可以直接预处理(后面会提到),但是对于 这类问题,直接预处理的代价太大,于是就可以考虑二次离线处理。
首先考虑预处理 。记 表示 中有多少数与 配对, 表示 中有多少数与 配对。显然,有 。
对于等式 ,两边同时异或 ,由于 ,得到 ,而 就是所有满足二进制下有 位为 的数。由于 ,所以 一共有 个,也就是最多只有 个。
考虑从 枚举 ,再枚举所有的 ,令 ,由于 和 只存在 的差别,可以直接略去第一维。此时, 就表示 中有多少数与 配对,那么就可以直接更新 。至此预处理完毕,时间复杂度为 。
莫队转移时,分为四类讨论需要二次离线的询问。
从 转移到 。总增加的贡献为 。前面一部分直接用 转移即可。后面一部分则记录到 的询问中二次离线处理。
从 转移到 。思路同上,但是需要注意此时是减去贡献,所以需要改变一下符号,具体实现可以看代码。
3.从 转移到 。总增加的贡献为 。此时需要注意,尽管 的情况在题意中是不合法的,但是此时仍然需要特判 的情况,因为尽管 中不包含异或自己的情况,但是后面会提到,在二次离线是是会把这类情况算进去的。
4.从 转移到 。思路同上,也别忘了变符号。
下面讨论二次离线的写法。
对于每一个 ,设前面莫队的时候需要加上一个 (其中 ,表示整体的正负符号)。此时 和 的大小关系就是不确定的,也就是上面提到为什么要特判 的情况。此时注意到区间 是固定的,那么按照预处理时的方式,在解决询问时就可以得到所有 的值(具体实现可以参考代码)。此时直接枚举 更新答案,根据莫队的性质,每次询问的区间 就是在离线莫队时相邻两个询问在一个方向(左边或右边)相差的范围,总的更新次数不会超过 。至此就得到了一个复杂度为 的算法。
最后,需要注意一下,在莫队时记录的答案只是与上一个区间的差值,相当于差分数组,所以还需要做一次前缀和。同时,答案别忘了开** long long**。
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e5+10;
#define LL long long
int g[N],f[N],len,n,m,num[N],k,tot,a[N]; LL ans[N],res[N];
struct node{int l,r,id;}q[N];
struct twice{int id,l,r,sign;};
vector<twice>range[N];
bool cmp(node x,node y)
{
int i=x.l/len,j=y.l/len; return i!=j?i<j: x.r<y.r;
}
int count(int x){int res=0; for(int i=0;i<14;i++) res+=(x>>i)&1;return res;}
int main()
{
scanf("%d%d%d",&n,&m,&k);for(int i=1;i<=n;i++) scanf("%d",&a[i]);len=sqrt(n);
for(int i=1;i<=m;i++) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;sort(q+1,q+m+1,cmp);
for(int i=0;i<(1<<14);i++) if(count(i)==k) num[++tot]=i;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=tot;j++) ++g[a[i]^num[j]];
f[i]=g[a[i+1]];
}
for(int i=1,L=1,R=0;i<=m;i++)
{
int l=q[i].l,r=q[i].r;
if(R<r) range[L-1].push_back(twice{i,R+1,r,-1});
while(R<r) res[i]+=f[R++];
if(R>r) range[L-1].push_back(twice{i,r+1,R,1});
while(R>r) res[i]-=f[--R];
if(L<l) range[R].push_back(twice{i,L,l-1,-1});
while(L<l) res[i]+=f[L-1]+(!k),L++;
if(L>l) range[R].push_back(twice{i,l,L-1,1});
while(L>l) res[i]-=f[L-2]+(!k),L--;
}
memset(g,0,sizeof(g));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=tot;j++) ++g[a[i]^num[j]];
for(int j=0;j<range[i].size();j++)
for(int k=range[i][j].l;k<=range[i][j].r;k++)
res[range[i][j].id]+=g[a[k]]*range[i][j].sign;
}
for(int i=2;i<=n;i++) res[i]+=res[i-1];
for(int i=1;i<=n;i++) ans[q[i].id]=res[i];
for(int i=1;i<=n;i++) printf("%lld\n",ans[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通