【模板】莫队二次离线

传送门


题解

有的题目本身可以用莫队来做,但是更新或者查询复杂度较高,导致直接使用莫队在这些问题上的表现不算优秀。

在询问的东西可以进行前后缀差分,且存在一个询问复杂度较低的做法的时候,可以考虑这个trick:莫队二次离线。stO %%% lxl %%% Orz

考虑莫队的实际操作过程中我们每一步在干什么,其实算的就是排序后的上一个询问和这一个询问的答案之间的差量。

现在我们考虑计算出所有差量之后来一个前缀和就可以得到所有询问的答案了。

于是我们要考虑其实就是四种端点平移的时候带来的贡献。

以左端点左移为例,剩下的三种直接类比考虑即可。

当前区间为 [ l , r ] [l,r] [l,r],需要把左端点平移到 n l nl nl,更新答案。

f ( i , [ l , r ] ) f(i,[l,r]) f(i,[l,r])表示 [ l , r ] [l,r] [l,r]区间中的信息与点 i i i的信息对答案的贡献。

很容易地,我们发现在平移过程中新增的答案就是 ∑ i = n l l − 1 f ( i , [ i + 1 , r ] ) \sum\limits_{i=nl}^{l-1}f(i,[i+1,r]) i=nll1f(i,[i+1,r])

前面说了要求能够差分,即任何时候, f f f满足 f ( i , [ l , n ] ) − f ( i , [ r + 1 , n ] ) = f ( i , [ l , r ] ) f(i,[l,n])-f(i,[r+1,n])=f(i,[l,r]) f(i,[l,n])f(i,[r+1,n])=f(i,[l,r])

于是我们发现现在只有两种询问了,分别是 f ( i , [ i + 1 , n ] ) f(i,[i+1,n]) f(i,[i+1,n]) f ( i , [ r + 1 , n ] ) f(i,[r+1,n]) f(i,[r+1,n])

对于第一种,我们直接从后往前扫描线的同时记录一下后缀和,然后加加减减即可。

对于第二种,我们在扫描线从后面扫到 r + 1 r+1 r+1的时候暴力枚举 i = n l , n l + 1 , … , l − 1 i=nl,nl+1,\dots,l-1 i=nl,nl+1,,l1计算即可,由于按照莫队的策略进行了排序,这里的均摊复杂度不会过高。

于是这样一类问题就可以利用扫描线+莫队解决了。

对于上面的洛谷版题,预处理所有有 k k k 1 1 1的数,然后利用上面的套路在扫描线的同时处理就行了。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	template<typename T>T get(){
		char c;T num;while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=((num+(num<<2))<<1)+(c^48);
		return num;
	}inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int N=1e5+7;

int n,m,k,B;int blk[N];
int bin[16395],bit[16395],ct;
struct Query{int l,r,i;}q[N];
struct Qry{int ql,qr,i,t;};
struct Qsm{int i,t;};
std::vector<Qry> pr[N],sf[N];
std::vector<Qsm> Pr[N],Sf[N];
int a[N];ll ans[N],sum;

void ins(int a){
	for(int re i=1;i<=ct;++i)++bin[a^bit[i]];
}

void work(){
	n=gi(),m=gi(),k=gi();if(!m)return ;
	for(int re i=1;i<=n;++i)a[i]=gi();
	for(int re i=0;i<16384;++i){
		bin[i]=bin[i>>1]+(i&1);
		if(bin[i]==k)bit[++ct]=i;
	}
	for(int re i=1;i<=m;++i)q[i].l=gi(),q[i].r=gi(),q[i].i=i;
	B=(n/sqrt(m)+5)*0.5;for(int re i=1;i<=n;++i)blk[i]=(i-1)/B+1;
	std::sort(q+1,q+m+1,[](cs Query &a,cs Query &b){
		return blk[a.l]!=blk[b.l]?blk[a.l]<blk[b.l]:(a.r!=b.r&&((blk[a.l]&1)^(a.r>b.r)));
	});
	for(int re i=1,l=1,r=0;i<=n;++i){
		int nl=q[i].l,nr=q[i].r,id=q[i].i;
		if(nl!=l){
			Sf[nl].push_back({id,1});
			Sf[l].push_back({id,-1});
		}
		if(nr!=r){
			Pr[nr].push_back({id,1});
			Pr[r].push_back({id,-1});
		} 
		if(nl<l)sf[r+1].push_back({nl,l-1,id,-1}),l=nl;
		if(nr>r)pr[l-1].push_back({r+1,nr,id,-1}),r=nr;
		if(nl>l)sf[r+1].push_back({l,nl-1,id,1}),l=nl;
		if(nr<r)pr[l-1].push_back({nr+1,r,id,1}),r=nr;
	}
	memset(bin,0,sizeof bin),sum=0;
	for(int re i=1;i<=n;++i){
		sum+=bin[a[i]];ins(a[i]);
		for(auto t:Pr[i])ans[t.i]+=sum*t.t;
		for(auto t:pr[i])for(int re j=t.ql;j<=t.qr;++j)ans[t.i]+=t.t*bin[a[j]];
	}
	memset(bin,0,sizeof bin),sum=0;
	for(int re i=n;i;--i){
		sum+=bin[a[i]];ins(a[i]);
		for(auto t:Sf[i])ans[t.i]+=sum*t.t;
		for(auto t:sf[i])for(int re j=t.ql;j<=t.qr;++j)ans[t.i]+=t.t*bin[a[j]];
	}
	for(int re i=2;i<=m;++i)ans[q[i].i]+=ans[q[i-1].i];
	for(int re i=1;i<=m;++i){
		cout<<ans[i]<<"\n";
	}
}

void file(){
#ifdef zxyoi
	freopen("blocks.in","r",stdin);
#endif
}
signed main(){file();work();return 0;}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值