把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

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

点此看题面

  • 给定一个长度为\(n\)的序列以及一个常数\(k\)
  • \(q\)次询问,每次求一个区间内有多少对\(i,j\)满足\(i<j\)\(a_i\oplus a_j\)二进制下恰有\(k\)\(1\)
  • \(n,q\le10^5,0\le 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_i\oplus a_j=x\)显然等价于\(a_i\oplus x=a_j\),所以要加入一个数的贡献只要枚举所有\(k\)\(1\)的数与它异或更新计数数组即可,然后询问就可以直接\(O(1)\)调用了。

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

代码:\(O(n\sqrt 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('\n');}
}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;
}
posted @ 2021-03-29 08:12  TheLostWeak  阅读(62)  评论(0编辑  收藏  举报