莫队学习笔记

介绍

莫队由莫涛发明,是一种处理区间询问的离线算法。


莫队

用两个指针 l , r l,r l,r来维护区间 [ l , r ] [l,r] [l,r]内的答案。

使用分块,设每块大小为 b b b,则有 n b \dfrac nb bn块。将询问离线下来,以 l l l所在块为第一关键字, r r r为第二关键字来排序。

  • 对于每一个块, r r r是递增的,最多移动 n n n次,则总共移动 n 2 b \dfrac{n^2}{b} bn2
  • l l l在同一块内单次移动最多移动 b b b次,总共移动 m b mb mb
  • 跨快移动, l l l最多移动 2 b 2b 2b次, r r r最多移动 n n n次,最多跨块 n b \dfrac nb bn次,总共移动 ( n 2 b + 2 n ) (\dfrac{n^2}{b}+2n) (bn2+2n)

三部分加起来,时间复杂度为 O ( n 2 b + m b ) O(\dfrac{n^2}{b}+mb) O(bn2+mb)。当 b = n m b=\dfrac{n}{\sqrt m} b=m n时最优,为 O ( n m ) O(n\sqrt m) O(nm )

一般情况下,令 b = n b=\sqrt n b=n ,时间复杂度为 O ( ( m + n ) n ) O((m+n)\sqrt n) O((m+n)n )

code

struct node{
	int l,r,id;
}w[N];
bool cmp(node ax,node bx){
	int a1=ax.l/bl,b1=bx.l/bl; //bl为块数 
	if(a1!=b1) return a1<b1;
	return ax.r<bx.r;
}
void add(int x){
	
}
void dele(int x){
	
}
void solve(){
	sort(w+1,w+n+1,cmp);
	int l=1,r=0;
	for(int i=1;i<=n;i++){
		int x=w[i].l,y=w[i].r;
		while(l>x) add(--l);
		while(l<x) dele(l++);
		while(r>y) dele(r--);
		while(r<y) add(++r);
		ans[w[i].id]=now;
	}
}

注意

		while(l>x) add(--l);
		while(l<x) dele(l++);
		while(r>y) dele(r--);
		while(r<y) add(++r);

因为有删除操作,所以在 l > r + 1 l>r+1 l>r+1的情况下会出现删除了没有加入的数的情况。

如果用 s e t set set来维护,则会出问题,所以可以改为

		while(l>x) add(--l);
		while(r<y) add(++r);
		while(l<x) dele(l++);
		while(r>y) dele(r--);

奇偶排序

若用奇偶排序,代码会跑得快一些。

bool cmp(node ax,node bx){
	int a1=ax.l/bl,b1=bx.l/bl;
	if(a1!=b1) return a1<b1;
	if(a1&1) return ax.r<bx.r;
	return ax.r>bx.r;
}

例题

P1494 [国家集训队] 小 Z 的袜子

用桶来存每种颜色的袜子,求出抽出颜色相同的袜子的种数,再除以抽出任意两只袜子的种数即可。

code

#include<bits/stdc++.h>
using namespace std;
int n,m,bl;
long long re,c[50005],z[50005],ans[50005],v[50005];
struct node{
    int l,r,id;
}a[50005];
bool cmp(node ax,node bx){
    int a1=ax.l/bl,b1=bx.l/bl;
    if(a1!=b1) return a1<b1;
    return ax.r<bx.r;
}
void add(int x){
    re-=z[x]*(z[x]-1);
    ++z[x];
    re+=z[x]*(z[x]-1);
}
void dele(int x){
    re-=z[x]*(z[x]-1);
    --z[x];
    re+=z[x]*(z[x]-1);
}
void gcd(long long i,long long j){
    long long p=i,q=j;
    while(j>0){
        i%=j;swap(i,j);
    }
    printf("%lld/%lld\n",p/i,q/i);
}
int main()
{
    scanf("%d%d",&n,&m);
    bl=sqrt(n);
    for(int i=1;i<=n;i++){
        scanf("%lld",&c[i]);
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d",&a[i].l,&a[i].r);
        a[i].id=i;
        long long k=a[i].r-a[i].l+1;
        k=k*(k-1);
        v[i]=k;
    }
    sort(a+1,a+m+1,cmp);
    for(int k=1,i=0,j=1;k<=m;k++){
        int l=a[k].l,r=a[k].r;
        while(i<r) add(c[++i]);
        while(i>r) dele(c[i--]);
        while(j<l) dele(c[j++]);
        while(j>l) add(c[--j]);
        ans[a[k].id]=re;
    }
    for(int i=1;i<=m;i++){
        if(ans[i]==0){
            printf("0/1\n");
            continue;
        }
        gcd(ans[i],v[i]);
    }
    return 0;
}

参考博客:https://blog.youkuaiyun.com/KonjakuLAF/article/details/126387244?spm=1001.2014.3001.5502

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值