莫队 基础篇

普通莫队

DQUERY - D-query

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

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

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

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

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

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

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

#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]\) 的答案,在把 \(l\)\(r\) 指针移动一格的情况下,仍然可以在 \(O(1)\) 复杂度内求出答案。

带修莫队

数颜色 / 维护队列

这里先说一下,带修莫队的块长一般使用 \(n^{\frac{2}{3}}\)

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

事实上,原来的四种转移方式再加上 \([l,r,t-1],[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;
}
posted @ 2024-07-19 18:00  zxh923  阅读(4)  评论(0编辑  收藏  举报