莫队 基础篇

普通莫队

DQUERY - D-query

先想一下最朴素的暴力怎么写。显然可以用一个 cnt 数组记录每种元素的出现次数,然后如果这种元素是第一次出现,则增加答案,时间复杂度 O(nq)

然后考虑如果如何用一个已经求出来答案的询问推出另外一个询问的答案。

显然我们要增加一部分数和删掉一部分数。对于删掉的数如果其出现次数为 2,则答案减一;对于增加的数如果其出现次数为 0,则答案加一。

然后莫队就是,在上面那种一个一个移动指针的情况下求答案。但是要预先知道所有询问,把所有询问离线下来,然后排序。

所以,莫队是一种离线算法

现在,我们将长度为 n 的序列分为 n 个块,先按左端点所在块的编号从小到大排序,如果出现两个询问的这一项相同,则按右端点的位置从小到大排序,这样的时间复杂度是 O(nn) 的。

事实上是非常好理解的,所以直接看一下代码:

#include<bits/stdc++.h>
#define int long long
#define N 50005
#define M 200005
#define S 1000005
using namespace std;
int n,m,len,w[N],ans[M],cnt[S];
struct node{
    int id,l,r;
}q[M];
int get(int x){
    return x/len;
}
bool cmp(const node &a,const node &b){
    int i=get(a.l),j=get(b.l);
    if(i!=j)return i<j;
    return a.r<b.r;
}
void add(int x,int &res){
    if(!cnt[x])res++;
    cnt[x]++;
}
void del(int x,int &res){
    cnt[x]--;
    if(!cnt[x])res--;
}
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i];
    }
    cin>>m;
    len=max(1ll,(int)sqrt(n*n/m));
    for(int i=0;i<m;i++){
        int l,r;
        cin>>l>>r;
        q[i]={i,l,r};
    }
    sort(q,q+m,cmp);
    for(int i=0,j=1,k=0,res=0;k<m;k++){
        int id=q[k].id,l=q[k].l,r=q[k].r;
        while(i<r)add(w[++i],res);
        while(i>r)del(w[i--],res);
        while(j<l)del(w[j++],res);
        while(j>l)add(w[--j],res);
        ans[id]=res;
    }
    for(int i=0;i<m;i++){
        cout<<ans[i]<<'\n';
    }
    return 0;
}

代码是比较好理解的,不过注意四个移动指针的循环的顺序是有要求的,故不要随意更改顺序。

然后总结一下莫队的本质,就是在暴力的基础上把询问排个序,然后要求如果你知道询问 [l,r] 的答案,在把 lr 指针移动一格的情况下,仍然可以在 O(1) 复杂度内求出答案。

带修莫队

数颜色 / 维护队列

这里先说一下,带修莫队的块长一般使用 n23

带修莫队的差别和普通莫队事实上并不大,就是要加一个维度记录时间。

事实上,原来的四种转移方式再加上 [l,r,t1],[l,r,t+1] 两种转移方式就行了。

然后想一下事件不同怎么转移。大概就是判断一下当前这个修改的位置在不在我们的询问内,如果在就把要改的东西加上,原来的东西减掉。然后这是对询问的贡献,但是修改是事实存在的,所以我们交换一下修改前和修改后的东西,因为后面有可能要再换回来,这样相当于是记录了一下。

然后就可以直接看一下代码了:

#include<bits/stdc++.h> 
#define int long long
#define N 1500050
using namespace std;
int n,m,x,y,lim,a[N],cntq,cntr,ll,rr,mp[N],cu,ans[N];
char c;
struct node{
	int l,r,t,id;
}q[N];
struct edge{
	int l,r;
}r[N];
bool cmp(node a,node b){
	if(a.l/lim!=b.l/lim)return a.l/lim>b.l/lim;
	else if(a.r/lim!=b.r/lim)return a.r/lim>b.r/lim;
	else return a.t>b.t;
}
void add(int x){
	if(!mp[x])cu++;mp[x]++;
}
void del(int x){
	mp[x]--;if(!mp[x])cu--;
}
signed main(){
	cin>>n>>m;
	lim=n/cbrt(m);
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=m;i++){
		cin>>c>>ll>>rr;
		if(c=='Q'){
			q[++cntq]={ll,rr,cntr,cntq};
		}
		else{
			r[++cntr]={ll,rr};
		}
	}
	sort(q+1,q+cntq+1,cmp);
	int L=1,R=0,las=0;
	for(int i=1;i<=cntq;i++){
		while(R<q[i].r)add(a[++R]);
		while(R>q[i].r)del(a[R--]);
		while(L>q[i].l)add(a[--L]);
		while(L<q[i].l)del(a[L++]);
		while(las<q[i].t){
			las++;
			if(r[las].l>=L&&r[las].l<=R)add(r[las].r),del(a[r[las].l]);
			swap(a[r[las].l],r[las].r);
		}
		while(las>q[i].t){
			if(r[las].l>=L&&r[las].l<=R)add(r[las].r),del(a[r[las].l]);
			swap(a[r[las].l],r[las].r);
			las--;
		}
		ans[q[i].id]=cu;
	}
	for(int i=1;i<=cntq;i++)cout<<ans[i]<<'\n';
	return 0;
}

树上莫队

Count on a tree II

首先如果只有子树查询,只需要用每个点的 dfs 序代表树上的每个点然后直接做序列上的莫队。

但是这种方法并不适用于链查询。对于链查询,我们可以使用欧拉序。

具体地,我们设 firxx 第一次在欧拉序序列中出现的位置,lasxx 第二次在欧拉序序列中出现的位置。

对于查询 (x,y),我们不妨设 firxfiry。若 x,ylcax,则我们查询区间 [firx,firy],否则查询区间 [lasx,firy] 和两点的 lca。不难发现这个东西可以朴素莫队,于是就做完了。

代码:

#include<bits/stdc++.h> 
#define int long long
#define N 200005
#define pii pair<int,int>
#define x first
#define y second
#define mod 1000000007
#define pct __builtin_popcount
#define inf 2e18
#define eps 1e-10
using namespace std; 
int T=1,n,m,cnt,len,col[N],b[N],bel[N],res[N];
int h[N],e[N],ne[N],idx,ans;
int fir[N],las[N],id[N],tot;
int dep[N],fa[N],siz[N],son[N],top[N],c[N];
bool st[N];
struct qry{
	int l,r,p,id;
	bool operator<(const qry &t)const{
		if(bel[l]!=bel[t.l])return bel[l]<bel[t.l];
		return r<t.r;
	}
}q[N];
void add(int a,int b){
	e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void dfs1(int u,int f){
	dep[u]=dep[f]+1;
	fa[u]=f;
	siz[u]=1;
	fir[u]=++tot;
	id[tot]=u;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa[u])continue;
		dfs1(j,u);
		siz[u]+=siz[j];
		if(siz[j]>siz[son[u]])son[u]=j;
	}
	las[u]=++tot;
	id[tot]=u;
}
void dfs2(int u,int f){
	top[u]=f;
	if(son[u])dfs2(son[u],f);
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa[u]||j==son[u])continue;
		dfs2(j,j);
	}
}
int get_lca(int a,int b){
	while(top[a]!=top[b]){
		if(dep[top[a]]<dep[top[b]])swap(a,b);
		a=fa[top[a]];
	}
	return dep[a]<dep[b]?a:b;
}
void modify(int x){
	if(st[x]){
		c[col[x]]--;
		if(!c[col[x]])ans--;
	}
	else{
		if(!c[col[x]])ans++;
		c[col[x]]++;
	}
	st[x]^=1;
}
void solve(int cs){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>col[i];
		b[i]=col[i];
	}
	sort(b+1,b+n+1);
	cnt=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=n;i++){
		col[i]=lower_bound(b+1,b+cnt+1,col[i])-b;
	}
	memset(h,-1,sizeof h);
	for(int i=1;i<n;i++){
		int a,b;
		cin>>a>>b;
		add(a,b);add(b,a);
	}
	dfs1(1,0);
	dfs2(1,1);
	len=sqrt(tot);
	for(int i=1;i<=tot;i++){
		bel[i]=(i-1)/len+1;
	}
	for(int i=1;i<=m;i++){
		int l,r,p;
		cin>>l>>r;
		if(fir[l]>fir[r])swap(l,r);
		p=get_lca(l,r);
		if(l==p){
			q[i].l=fir[l];
			q[i].r=fir[r];
		}
		else{
			q[i].l=las[l];
			q[i].r=fir[r];
			q[i].p=p;
		}
		q[i].id=i;
	}
	sort(q+1,q+m+1);
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		while(l>q[i].l)modify(id[--l]);
		while(r<q[i].r)modify(id[++r]);
		while(l<q[i].l)modify(id[l++]);
		while(r>q[i].r)modify(id[r--]);
		if(q[i].p)modify(q[i].p);
		res[q[i].id]=ans;
		if(q[i].p)modify(q[i].p);
	}
	for(int i=1;i<=m;i++){
		cout<<res[i]<<'\n';
	}
}
void solution(){
	/*
	nothing here
	*/
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
//	srand(time(0));
//	cin>>T;
	for(int cs=1;cs<=T;cs++){
		solve(cs);
	}
	return 0;
}

综合题

糖果公园

树上带修莫队模板题。考虑按照上文所述的套路,先把树拍到欧拉序序列上,然后直接做带修莫队。

代码:

#include<bits/stdc++.h> 
#define int long long
#define N 202005
#define pii pair<int,int>
#define x first
#define y second
#define mod 1000000007
#define pct __builtin_popcount
#define inf 2e18
#define eps 1e-10
using namespace std; 
int T=1,n,m,Q,len,cnt,v[N],w[N],val[N],bel[N],res[N];
int dep[N],fa[N],siz[N],son[N],top[N];
int tot,fir[N],las[N],id[N],c[N],ans;
bool st[N];
vector<int>e[N];
struct node{
	int p,v;
}p[N];
struct qry{
	int l,r,p,t,id;
	bool operator<(const qry &u)const{
		if(bel[l]!=bel[u.l])return bel[l]<bel[u.l];
		if(r!=u.r)return r<u.r;
		return t<u.t;
	}
}q[N];
void add(int a,int b){
	e[a].push_back(b);
}
void dfs1(int u,int f){
	dep[u]=dep[f]+1;
	fa[u]=f;
	siz[u]=1;
	fir[u]=++tot;
	id[tot]=u;
	for(auto j:e[u]){
		if(j==fa[u])continue;
		dfs1(j,u);
		siz[u]+=siz[j];
		if(siz[j]>siz[son[u]])son[u]=j;
	}
	las[u]=++tot;
	id[tot]=u;
}
void dfs2(int u,int f){
	top[u]=f;
	if(son[u])dfs2(son[u],f);
	for(auto j:e[u]){
		if(j==fa[u]||j==son[u])continue;
		dfs2(j,j);
	}
}
int get_lca(int a,int b){
	while(top[a]!=top[b]){
		if(dep[top[a]]<dep[top[b]])swap(a,b);
		a=fa[top[a]];
	}
	return dep[a]<dep[b]?a:b;
}
void add(int p){
	c[val[p]]++;
	ans+=v[val[p]]*w[c[val[p]]];
}
void del(int p){
	ans-=v[val[p]]*w[c[val[p]]];
	c[val[p]]--;
}
void move(int p){
	if(st[p])del(p);
	else add(p);
	st[p]^=1;
}
void modify(int x){
	if(st[p[x].p]){
		move(p[x].p);
		swap(val[p[x].p],p[x].v);
		move(p[x].p);
	}
	else swap(val[p[x].p],p[x].v);
}
void solve(int cs){
	cin>>n>>m>>Q;
	for(int i=1;i<=m;i++){
		cin>>v[i];
	}
	for(int i=1;i<=n;i++){
		cin>>w[i];
	}
	for(int i=1;i<n;i++){
		int a,b;
		cin>>a>>b;
		add(a,b);add(b,a);
	}
	for(int i=1;i<=n;i++){
		cin>>val[i];
	}
	dfs1(1,0);
	dfs2(1,1);
	len=cbrt(tot)*cbrt(tot);
	cnt=ceil(1.0*tot/len);
	for(int i=1;i<=cnt;i++){
		for(int j=(i-1)*len+1;j<=i*len;j++){
			bel[j]=i;
		}
	}
	int cntp=0,cntq=0;
	for(int i=1;i<=Q;i++){
		int op,l,r,P;
		cin>>op>>l>>r;
		if(op){
			P=get_lca(l,r);
			cntq++;
			q[cntq].t=cntp;
			q[cntq].id=cntq;
			if(fir[l]>fir[r])swap(l,r);
			if(l==P){
				q[cntq].l=fir[l];
				q[cntq].r=fir[r];
			}
			else{
				q[cntq].l=las[l];
				q[cntq].r=fir[r];
				q[cntq].p=P;
			}
		}
		else{
			cntp++;
			p[cntp].p=l;
			p[cntp].v=r;
		}
	}
	sort(q+1,q+cntq+1);
	int l=1,r=0,t=0;
	for(int i=1;i<=cntq;i++){
		while(l>q[i].l)move(id[--l]);
		while(r<q[i].r)move(id[++r]);
		while(l<q[i].l)move(id[l++]);
		while(r>q[i].r)move(id[r--]);
		while(t<q[i].t)modify(++t);
		while(t>q[i].t)modify(t--);
		if(q[i].p)move(q[i].p);
		res[q[i].id]=ans;
		if(q[i].p)move(q[i].p);
	}
	for(int i=1;i<=cntq;i++){
		cout<<res[i]<<'\n';
	}
}
void solution(){
	/*
	nothing here
	*/
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
//	srand(time(0));
//	cin>>T;
	for(int cs=1;cs<=T;cs++){
		solve(cs);
	}
	return 0;
}
posted @   zxh923  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示