「刷题笔记」莫队

莫队算法——从入门到黑题 by WAMonster
(感谢yspm的推荐

板子

对位置进行分块,记录询问每个端点所属的块,排序时,左端点按所属的块排序,右端点按原位置排序
淦发现之前在博客里发出了“莫队左端点移动O(n)”的暴论
排序后,左端点移动的次数是(O(nsqrt{n}))的,
而右端点在每个相同左端点中递增,移动的次数是(O(nsqrt{n})),所以算法总复杂度为(O(nsqrt{n}))
还有一些奇怪的优化比如奇偶性排序,但是并不会证只能先背过了……

SP3267 DQUERY - D-query

code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define N 30005
#define Q 200005
#define C 1000005

ll n,a[N],bel[N];
ll q;
struct query
{
	ll a,b,id;
	ll aa,bb;
	bool operator<(const query &y)const
	{
		return aa==y.aa?b<y.b:aa<y.aa;
	}
}qq[Q];

inline ll read()
{
	ll f=0,s=0; char c=getchar();
	while(c>'9'||c<'0')f=(c=='-'),c=getchar();
	while(c>='0'&&c<='9')s=(s<<3)+(s<<1)+(c^'0'),c=getchar();
	return f?-s:s;
}

ll cnt[C];
ll an=0;
ll ans[Q];

void add(ll x)
{
	if(!cnt[a[x]])an++;
	cnt[a[x]]++;
}

void del(ll x)
{
	cnt[a[x]]--;
	if(!cnt[a[x]])an--;
}

int main()
{
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	ll S=sqrt(n)+1;
	for(int i=1;i<=n;i++)bel[i]=(i-1)/S+1;
	q=read();
	for(int i=1;i<=q;i++)
	{
		qq[i].a=read(),qq[i].b=read(),qq[i].id=i;
		qq[i].aa=bel[qq[i].a],qq[i].bb=bel[qq[i].b];
	}
	sort(qq+1,qq+q+1);
	ll l=1,r=0;
	for(int i=1;i<=q;i++)
	{
		ll nl=qq[i].a,nr=qq[i].b;
		while(nl>l)del(l++);
		while(nl<l)add(--l);
		while(nr>r)add(++r);
		while(nr<r)del(r--);
		ans[qq[i].id]=an;
	}
	for(int i=1;i<=q;i++)printf("%lld
",ans[i]);
	return 0;
}

luogu P2709 小B的询问

一个挺有用的统计平方和的套路:从(a^2)((a+1)^2),即为加上(2a+1)
所以只需要统计每种颜色当前区间内的出现次数就好

二维莫队

蔬菜

原理和一维差不多,只不过是相当于区间变成了矩形,所以移动的时候要对一个区间进行加加减减
用二维莫队就可以转化成和小B的询问套路相同的题

带修莫队

P1903 [国家集训队]数颜色 / 维护队列

与普通莫队的区别在于支持修改,当然莫队需要离线
若强制在线就得考虑别的方法了,但是如果支持修改且可以离线有时就可以考虑带修莫队
为了支持修改,带修莫队需要在普通莫队的基础上加一个时间戳,时间戳记录的是每个询问上一个修改操作的时间,
把修改操作单独储存后,就可以通过时间戳的加减来实现对于某个区间答案的时间变换
感性理解:就是由原来的((l+1,r))((l-1,r))((l,r+1))((l,r-1))四个方向的转移
变成了((l+1,r,t))((l-1,r,t))((l,r+1,t))((l,r-1,t))((l,r,t+1))((l,r,t-1))六个方向的转移
转移的时候需要记录当前值以备转移回去,可以直接把当前值与修改操作中的修改值交换,这样回来的时候就能直接换回去
奇偶性排序直接12s->5s是什么操作

code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define N 133350
#define C 1000005

ll n,m;
ll a[N];
ll cnt[C],ans[N];

struct query
{
	ll l,r,t,id;
	ll lb,rb;
	bool operator<(const query &y)const
	{
		return (lb^y.lb)?lb<y.lb:(rb^y.rb)?rb<y.rb:t<y.t;
	}
}q[N];
ll qcn=0;

struct update
{
	ll x,v;
}c[N];

inline ll read()
{
	ll f=0,s=0; char c=getchar();
	while(c>'9'||c<'0')f=(c=='-'),c=getchar();
	while(c>='0'&&c<='9')s=(s<<3)+(s<<1)+(c^'0'),c=getchar();
	return f?-s:s;
}

ll ti=0,an=0;
char ch[2];

void add(ll x=0,ll c=0)
{
	if(x)
	{
		if(cnt[a[x]]==0)an++;
		cnt[a[x]]++;
	}
	else if(c)
	{
		if(cnt[c]==0)an++;
		cnt[c]++;
	}
}

void del(ll x=0,ll c=0)
{
	if(x)
	{
		cnt[a[x]]--;
		if(cnt[a[x]]==0)an--;
	}
	else if(c)
	{
		cnt[c]--;
		if(cnt[c]==0)an--;
	}
}

int main()
{
	n=read(); m=read();
	ll S=pow(n,3.0/4.0);
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=m;i++)
	{
		scanf("%s",ch);
		if(ch[0]=='Q')
		{
			q[++qcn].l=read(),q[qcn].r=read(),q[qcn].t=ti,q[qcn].id=qcn;
			q[qcn].lb=(q[qcn].l-1)/S+1,q[qcn].rb=(q[qcn].r-1)/S+1;
		}
		else if(ch[0]=='R')
		{
			c[++ti].x=read(),c[ti].v=read();
		}
	}
	sort(q+1,q+qcn+1);
	ll l=1,r=0,t=0;
	for(int i=1;i<=qcn;i++)
	{
		ll nl=q[i].l,nr=q[i].r,lt=q[i].t;
		while(l<nl)del(l++,0);
		while(l>nl)add(--l,0);
		while(r<nr)add(++r,0);
		while(r>nr)del(r--,0);
		while(t>lt)
		{
			if(nl<=c[t].x&&c[t].x<=nr)del(c[t].x,0),add(0,c[t].v);
			swap(a[c[t].x],c[t].v);
			t--;
		}
		while(t<lt)
		{
			t++;
			if(nl<=c[t].x&&c[t].x<=nr)del(c[t].x,0),add(0,c[t].v);
			swap(a[c[t].x],c[t].v);
		}
		ans[q[i].id]=an;
	}
	for(int i=1;i<=qcn;i++)printf("%lld
",ans[i]);
	return 0;
}

树上莫队

SP10707 COT2

考虑怎样把树上的点转化成一个容易用莫队处理的序列,那么可以使用欧拉序
考虑路径((u,v)(dep_u<dep_v))上的点在欧拉序里是怎么表现的
首先,忽略在一段欧拉序中出现两次的点,这些点不在所求路径上
记点(k)在欧拉序中第一次出现在(fi_k),最后一次出现在(la_k),那么:

  • 如果(lca(u,v)=u),那么所求区间为([fi_u,fi_v])
  • 否则,所求区间为([la_u,fi_v]),但这个区间不包含他们的lca,这时lca要单独处理

这样,就可以用莫队处理树上的问题了

code:
#include<bits/stdc++.h>
using namespace std;
#define ll int
#define ull unsigned long long
#define N 200005
#define gc() (p1==p2?(p2=buf+fread(p1=buf,1,1<<20,stdin),p1==p2?EOF:*p1++):*p1++)
char buf[1<<20],*p1,*p2;

ll n,m;
ll w[N],b[N],tot=0;
ll t1,t2;

ll v[N];
ll h[N],nx[N],tt=0;

void add(ll a,ll b){
	v[++tt]=b;
	nx[tt]=h[a];
	h[a]=tt;
}

ll bel[N];
struct query{
	ll l,r,id,lc;
	ll lb,rb;
	bool operator<(const query &b)const{
		return (bel[l]^bel[b.l])?(bel[l]<bel[b.l]):((bel[l]&1)?r<b.r:r>b.r);
	}
}q[N];

ll a[N*2],fi[N],la[N],cnt=0;
ll fa[N][21],dep[N];
void dfs(ll u,ll f){
	a[++cnt]=u;
	fa[u][0]=f;
	dep[u]=dep[f]+1;
	fi[u]=cnt;
	for(int i=1;i<=20;i++){
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	for(int i=h[u];i;i=nx[i]){
		if(v[i]==f)continue;
		dfs(v[i],u);
	}
	a[++cnt]=u;
	la[u]=cnt;
}

inline int lca(int u,int v){
	if(dep[u]<dep[v])swap(u,v);
	for(int i=20;i>=0;--i)if(dep[u]-(1<<i)>=dep[v])u=fa[u][i];
	if(u==v)return u;
	for(int i=20;i>=0;--i)if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}

bool vis[N];
ll cn[N];
ll an=0,ans[N];
inline void work(int pos){
	vis[pos]?an-=!--cn[w[pos]]:an+=!cn[w[pos]]++;
	vis[pos]^=1;
}

inline ll read(){
	ll f=0,s=0; char c=gc();
	while(c>'9'||c<'0')f=(c=='-'),c=gc();
	while(c>='0'&&c<='9')s=(s<<3)+(s<<1)+(c^'0'),c=gc();
	return f?-s:s;
}

int main(){
	n=read(), m=read();
	for(int i=1;i<=n;i++)w[i]=read(),b[++tot]=w[i];
	sort(b+1,b+tot+1); tot=unique(b+1,b+tot+1)-b-1;
	for(int i=1;i<=n;i++)w[i]=lower_bound(b+1,b+tot+1,w[i])-b;//cout<<w[i]<<' ';cout<<endl;
	for(int i=1;i<n;i++)t1=read(),t2=read(),add(t1,t2),add(t2,t1);
	dfs(1,0);
	ll S=sqrt(cnt),bnum=ceil((double)cnt/S);
	for(int i=1;i<=bnum;++i)for(int j=S*(i-1)+1;j<=i*S;++j){
		bel[j]=i;
	}
	for(int i=1;i<=m;i++){
		q[i].l=read(),q[i].r=read(),q[i].id=i;
		ll tlc=lca(q[i].l,q[i].r);
		if(fi[q[i].l]>fi[q[i].r])swap(q[i].l,q[i].r);
		if(tlc==q[i].l)q[i].l=fi[q[i].l],q[i].r=fi[q[i].r];
		else q[i].l=la[q[i].l],q[i].r=fi[q[i].r],q[i].lc=tlc;
	}
	sort(q+1,q+m+1);
	ll l=1,r=0;
	for(int i=1;i<=m;i++){
		ll nl=q[i].l,nr=q[i].r;
		while(l<nl)work(a[l++]);
		while(l>nl)work(a[--l]);
		while(r<nr)work(a[++r]);
		while(r>nr)work(a[r--]);
		if(q[i].lc)work(q[i].lc);
		ans[q[i].id]=an;
		if(q[i].lc)work(q[i].lc);
	}
	for(int i=1;i<=m;i++)printf("%d
",ans[i]);
	return 0;
}

需要注意的:

  • 莫队中的分块,在点的编号较小,询问较多时,先统一求出每个数所属的块,不用每个询问单独算
  • lca倍增预处理(lg)数组会变慢,最好直接根据数据范围定下上界(21,31这种的)

要是这两点出了岔子程序效率就会极其低……

回滚莫队

有时候题目中并不是很好实现莫队移动区间时的“删除”操作,这时可以用回滚莫队来绕开删除的不便
具体的实现是把询问按左端点所属的块排序,块内按右端点编号排序,左右端点在同一块就暴力算,否则右端点从当前块右端出发向右走(这个移动排序后是单调的),左端点每次从当前块右端向左走,处理完每个询问后再把左端点移到块右端,并把答案恢复到上一次左端点走之前的状态
具体细节见代码

洛谷 P5906 【模板】回滚莫队&不删除莫队
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 200005

ll n,m,a[N],b[N],S,tot=0;
ll bel[N];

struct query{
	ll l,r,id;
	inline bool operator<(const query &b)const{
		return (bel[l]==bel[b.l])?(r<b.r):(bel[l]<bel[b.l]);
	}
}q[N];
ll pre[N],lst[N],ans[N];
ll st[N],tp=0;

inline ll work(ll l,ll r){ ll an=0;
	for(int i=l;i<=r;i++){
		if(!pre[a[i]])pre[a[i]]=i,st[++tp]=a[i];
		an=max(an,i-pre[a[i]]);
	}
	while(tp)pre[st[tp]]=0,tp--;
	return an;
}

inline ll read(){
	ll f=0,s=0; char c=getchar();
	while(c>'9'||c<'0')f=(c=='-'),c=getchar();
	while(c>='0'&&c<='9')s=(s<<3)+(s<<1)+(c^'0'),c=getchar();
	return f?-s:s;
}

int main(){
	n=read(); for(int i=1;i<=n;i++)a[i]=b[++tot]=read();
	m=read(); for(int i=1;i<=m;i++)q[i].l=read(), q[i].r=read(), q[i].id=i;
	S=sqrt(n)+1; for(int i=1;i<=n;i++)bel[i]=(i-1)/S+1;
	sort(b+1,b+tot+1); tot=unique(b+1,b+tot+1)-b-1;
	for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
	sort(q+1,q+m+1);
	for(int i=1,j=1;i<=S;i++){ ll br=i*S, l=br+1, r=br, an=0;
		for(;bel[q[j].l]==i;j++){ ll ql=q[j].l, qr=q[j].r;
			if(bel[ql]==bel[qr]){ ans[q[j].id]=work(ql,qr); continue; }
			while(r<qr){
				r++; lst[a[r]]=r;
				if(!pre[a[r]])pre[a[r]]=r,st[++tp]=a[r]; an=max(an,r-pre[a[r]]);
			}
			ll tmp=an;
			while(l>ql){
				l--; if(!lst[a[l]])lst[a[l]]=l,st[++tp]=a[l];
				an=max(an,lst[a[l]]-l);
			}
			ans[q[j].id]=an;
			while(l<=br){
				if(lst[a[l]]==l)lst[a[l]]=0; l++;
			}
			an=tmp;
		}
		while(tp)pre[st[tp]]=lst[st[tp]]=0,tp--;
	}
	for(int i=1;i<=m;i++)printf("%lld
",ans[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/zzzuozhe-gjy/p/14017090.html