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

点此看题面

  • 给定一个长度为(n)的序列以及一个常数(k)
  • (q)次询问,每次求一个区间内有多少对(i,j)满足(i<j)(a_ioplus a_j)二进制下恰有(k)(1)
  • (n,qle10^5,0le V<2^{14})

二次离线莫队

其实之前写过一次二次离线莫队,是求区间逆序对。感觉这道题要好写的多。

就是考虑我们移动区间的复杂度比较大,但实际上我们一次把莫队右端点(R)移到当前询问的右端点(qr),答案的变化量就是(R+1)([L,R])(R+2)([L,R+1]),...,(qr)([L,qr-1])能产生的贡献。

我们把这个贡献差分,就变成(R+1)([1,R]),...,(qr)([1,qr-1])的贡献和减去(R+1)([1,L-1]),...,(qr)([1,L-1])的贡献和。

发现对于第一部分的贡献,区间的右端点都是询问点(-1),可以直接计算出所有的这类贡献。

而对于第二部分的贡献,左端点相同,考虑直接把这个区间([R+1,qr])扔到(L-1)的一个(vector)里面,之后我们第二次离线,枚举每个前缀加入其中全部数处理对应(vector)中的区间询问(这里的区间询问可以直接暴枚区间中每个数一个一个询问,就相当于是莫队复杂度)。

要计算加入一个数的贡献,发现二进制下恰有(k)(1)的数最多只有(C_{14}^7)个,而(a_ioplus a_j=x)显然等价于(a_ioplus x=a_j),所以要加入一个数的贡献只要枚举所有(k)(1)的数与它异或更新计数数组即可,然后询问就可以直接(O(1))调用了。

其余四种端点移动也是类似的,就是要注意移动左端点的时候是用(L)([1,R])的贡献减去(L)([1,L])的贡献,端点不固定的那类区间的右端点是询问点而非询问点(-1),因此当(k=0)的时候要记得算上一个位置与自己产生的贡献。

代码:(O(nsqrt n))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define V 16384
#define LL long long
using namespace std;
int n,m,k,a[N+5],sz,bl[N+5],s[V+5],c[V+5];LL f[N+5],g[N+5],ans[N+5];
struct Q {int p,l,r;I bool operator < (Con Q& o) Con {return bl[l]^bl[o.l]?l<o.l:r<o.r;}}q[N+5];
struct Q2 {int p,l,r,op;I Q2(CI i=0,CI a=0,CI b=0,CI c=0):p(i),l(a),r(b),op(c){}};vector<Q2> v[N+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('
');}
}using namespace FastIO;
I int Cnt(RI x) {RI t=0;W(x) x&=x-1,++t;return t;}
int main()
{
	RI i;for(read(n,m,k),sz=sqrt(n),i=1;i<=n;++i) read(a[i]),bl[i]=(i-1)/sz+1;
	RI t=0;for(i=0;i^V;++i) Cnt(i)==k&&(s[++t]=i);//预处理出所有恰有k个1的数
	for(i=1;i<=m;++i) read(q[i].l,q[i].r),q[i].p=i;
	RI L=1,R=0;for(sort(q+1,q+m+1),i=1;i<=m;++i)//莫队
		R<q[i].r&&(v[L-1].push_back(Q2(q[i].p,R+1,q[i].r,-1)),R=q[i].r),
		L>q[i].l&&(v[R].push_back(Q2(q[i].p,q[i].l,L-1,1)),L=q[i].l),
		R>q[i].r&&(v[L-1].push_back(Q2(q[i].p,q[i].r+1,R,1)),R=q[i].r),
		L<q[i].l&&(v[R].push_back(Q2(q[i].p,L,q[i].l-1,-1)),L=q[i].l);
	RI j,p,vs;for(i=1;i<=n;++i) {for(f[i]=f[i-1]+c[a[i]],g[i]=f[i]+(k?0:i),j=1;j<=t;++j) ++c[a[i]^s[j]];//离线枚举前缀
		for(j=0,vs=v[i].size();j^vs;++j) for(p=v[i][j].l;p<=v[i][j].r;++p) ans[v[i][j].p]+=v[i][j].op*c[a[p]];}//处理这个前缀上的区间询问
	for(L=1,R=0,i=1;i<=m;++i) ans[q[i].p]+=ans[q[i-1].p]+g[q[i].l-1]-g[L-1]+f[q[i].r]-f[R],L=q[i].l,R=q[i].r;//统计贡献总和,注意算出的是答案变化量要给ans做前缀和
	for(i=1;i<=m;++i) writeln(ans[i]);return clear(),0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4887.html