【洛谷】P4887 【模板】莫队二次离线

原题链接

题意

给定一个长度为 n 的序列和一个常数 k,每次询问一个区间 [l,r] 内,有多少对 i,j,满足 li<jr ,且 aiaj 的二进制表示下恰好k 位为 1

数据范围

1n,m105,0ai<214,0k14

思路

按照普通莫队的思路,设当前的指针为 L,R,询问的区间为 l,rSx,y 表示 1x 中有多少数与 ay 配对。以 R 转移到 R+1 为例,更新的答案就为 SR,R+1SL1,R+1 。对于形如 Sx,x+1 ,可以直接预处理(后面会提到),但是对于 SR,L1 这类问题,直接预处理的代价太大,于是就可以考虑二次离线处理。

首先考虑预处理 Sx,x+1。记 f[i] 表示 1i 中有多少数与 a[i+1] 配对,w[i][x] 表示 1i 中有多少数与 x 配对。显然,有 f[i]=w[i][a[i+1]]

对于等式 ab=y,两边同时异或 b,由于 bb=0,a0=a,得到 a=by,而 y 就是所有满足二进制下有 k 位为 1 的数。由于 ab<214,所以 y 一共有 C14k 个,也就是最多只有 3432 个。

考虑从 1n 枚举 ai,再枚举所有的 y,令 g[i][aiy]++,由于 g[i+1][x]g[i][x] 只存在 a[i+1] 的差别,可以直接略去第一维。此时,g[x] 就表示 1i 中有多少数与 x 配对,那么就可以直接更新 f[i]=g[a[i+1]]。至此预处理完毕,时间复杂度为 O(C14kn)

莫队转移时,分为四类讨论需要二次离线的询问。

1.R 转移到 r 。总增加的贡献为 i=Rr1Si,i+1SL1,i+1。前面一部分直接用 i=Rr1f[i] 转移即可。后面一部分则记录到 L1 的询问中二次离线处理。

2.r 转移到 R。思路同上,但是需要注意此时是减去贡献,所以需要改变一下符号,具体实现可以看代码。

3.从 L 转移到 l。总增加的贡献为 i=lL1SR,iSi,i。此时需要注意,尽管 i=i 的情况在题意中是不合法的,但是此时仍然需要特判 k=0 的情况,因为尽管 f[i] 中不包含异或自己的情况,但是后面会提到,在二次离线是是会把这类情况算进去的。

4.从 l 转移到 L。思路同上,也别忘了变符号。

下面讨论二次离线的写法。

对于每一个 i,设前面莫队的时候需要加上一个 pj=lrSi,j (其中 |p|=1,表示整体的正负符号)。此时 ji 的大小关系就是不确定的,也就是上面提到为什么要特判 k=0 的情况。此时注意到区间 1i 是固定的,那么按照预处理时的方式,在解决询问时就可以得到所有 Si,j 的值(具体实现可以参考代码)。此时直接枚举 j 更新答案,根据莫队的性质,每次询问的区间 [l,r] 就是在离线莫队时相邻两个询问在一个方向(左边或右边)相差的范围,总的更新次数不会超过 O(nm)。至此就得到了一个复杂度为 O(C14kn+nm) 的算法。

最后,需要注意一下,在莫队时记录的答案只是与上一个区间的差值,相当于差分数组,所以还需要做一次前缀和。同时,答案别忘了开** 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;
}
posted @   曙诚  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示