莫队算法学习

WAMonster大佬的博客

众所周知,莫队是由莫涛大神提出的,一种玄学毒瘤暴力骗分区间操作算法,它以简短的框架、简单易记的板子和优秀的复杂度闻名于世。然而由于莫队算法应用的毒瘤,很多可做的莫队模板题都有着较高的难度评级,令很多初学者望而却步。然而,如果你真正理解了莫队的算法原理,那么它用起来还是很简单的。当然某些套左套右的毒瘤除外

莫队算法主要是处理对本蒟蒻来说线段树不能处理的区间问题,如区间中的元素种类等。主席树,树套树那些什么的又不会写,而莫队算法理解起来相对容易。说实话这是本蒟蒻学习得最顺利的算法。目前我只学会了普通莫队与一点带修莫队。

莫队通过左右指针来维护一个区间,但指针左右反复移动效率太低。所以进行离线操作,通过分块、排序,使每次操作间距尽量小,指针尽量按同一顺序跳。

例题:

普通莫队
Luogu SP3267 DQUERY - D-query

莫队模板题。注意细节是初始化左指针l=1,右指针r=0,以免出现奇奇怪怪的错误。

#include<bits/stdc++.h>
using namespace std;

const int maxn=1000010;
int n,m,len,l,r,sum;
int num[maxn],be[maxn],cnt[maxn],ans[maxn];

struct node
{
	int l,r,id;
}q[maxn*7];

int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
	while(ch>='0'&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

bool cmp(node a,node b)
{
	return be[a.l]==be[b.l] ? a.r<b.r : be[a.l]<be[b.l];
}

void add(int x)
{
	if(cnt[num[x]]==0) ++sum;//数字num[x]在区间中数量由0增加,区间数字种类增加
	++cnt[num[x]];
}

void del(int x)
{
	--cnt[num[x]];
	if(cnt[num[x]]==0) --sum;//数字num[x]在区间中数量减少至0,区间数字种类减少
}

int main()
{
	//freopen("input.txt","r",stdin);
	n=read();
	len=sqrt(n);
	for(int i=1;i<=n;i++) 
	{
		num[i]=read();
		be[i]=i/len+1;
	}
	m=read();
	for(int i=1;i<=m;i++)
	{
		q[i].l=read();
		q[i].r=read();
		q[i].id=i;//记录序号以便按序输出答案
	}
	sort(q+1,q+1+m,cmp);
	l=1,r=0;
	for(int i=1;i<=m;i++)
	{
		int ql=q[i].l,qr=q[i].r;
		while(l<ql) del(l++);//指针移动,修改区间
		while(l>ql) add(--l);
		while(r<qr) add(++r);
		while(r>qr) del(r--);
		ans[q[i].id]=sum;
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	return 0;
}
P1494 [国家集训队]小Z的袜子

详见蒟蒻的博客
手推一下即可得组合数(等差数列)的算式。注意特判0/1的情况。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll maxn=50005;
ll n,m,len,sum;
ll num[maxn],be[maxn],cnt[maxn],tot[maxn];

struct node
{
	ll l,r,id;
}q[maxn],ans[maxn];

bool cmp(node a,node b)
{
	return be[a.l]==be[b.l] ? a.r<b.r : be[a.l]<be[b.l];
}

void add(ll x){
	sum+=cnt[num[x]];
	++cnt[num[x]];
}

void del(ll x){
	--cnt[num[x]];
	sum-=cnt[num[x]];
}

ll gcd(ll x,ll y)
{
	return y==0 ? x : gcd(y,x%y);
}

int main()
{
	//freopen("input.txt","r",stdin);
	//freopen("output.txt","w",stdout);
	scanf("%lld%lld",&n,&m);
	len=sqrt(n);
	for(ll i=1;i<=n;i++)
	{
		scanf("%lld",&num[i]);
		be[i]=(i-1)/len+1;
	}
	for(ll i=1;i<=m;i++)
	{
		scanf("%lld%lld",&q[i].l,&q[i].r);
		q[i].id=i;
	}
	sort(q+1,q+m+1,cmp);
	ll l=1,r=0;
	for(ll i=1;i<=m;i++)
	{
		ll ql=q[i].l,qr=q[i].r;
		while(l<ql) del(l++);
		while(l>ql) add(--l);
		while(r<qr) add(++r);
		while(r>qr) del(r--);
		ll div=((qr-ql+1)*(qr-ql+1)-(qr-ql+1))/2,fac=1;
		if(sum) fac=gcd(sum,div);
		else div=1;
		ans[q[i].id].l=sum/fac,ans[q[i].id].r=div/fac;
	}
	for(ll i=1;i<=m;i++) printf("%lld/%lld\n",ans[i].l,ans[i].r);
	return 0;
 } 
带修莫队
P1903 [国家集训队]数颜色 / 维护队列

带修莫队在普通莫队的基础上增加了一个时间戳time,在更新询问操作的区间时把该操作时间戳以前一段时间的修改操作一起执行,直到时间戳同步。
注意只当修改操作的位置在当前区间内时才更新颜色数量cnt值,否则只改变其颜色而不统计入答案。
并且,因为前后的询问操作时间戳可能不连续,即因为按照左右端点所属分块排序,可能在更新完时间靠后的询问操作后又要更新时间靠前的询问操作。其间进行的修改操作可能还要改回来,故用swap(num[c[t].pos],c[t].col);来保存修改信息以便再改回来。
该题卡常,故需用优化。

// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;

int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}

typedef long long ll;
const int maxn=1500000;
int n,m,len,cq,cr,sum;
int num[maxn],be[maxn],cnt[maxn],ans[maxn];

struct node
{
    int l,r,id,tim;
}q[maxn];

struct edge
{
    int pos,col;
}c[maxn];

bool cmp(node a,node b)
{
    if(be[a.l]!=be[b.l]) return be[a.l]<be[b.l];
    if(be[a.r]!=be[b.r]) return be[a.r]<be[b.r];
    return a.tim<b.tim;
}

void del(int x)
{
    --cnt[num[x]];
    if(!cnt[num[x]]) --sum;
}

void add(int x)
{
    ++cnt[num[x]];
    if(cnt[num[x]]==1) ++sum;
}

int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","r",stdin);
    n=read(),m=read();
    len=pow(n,2.0/3.0);
    for(int i=1;i<=n;i++)
    {
        num[i]=read();
        be[i]=(i-1)/len+1;
    }
    char op[5];
    for(int i=1;i<=m;i++)
    {
        scanf("%s",op);
        if(op[0]=='Q')
        {
            q[++cq].l=read();
            q[cq].r=read();
            q[cq].tim=cr;
            q[cq].id=cq;
        }
        else 
        {
            c[++cr].pos=read();
            c[cr].col=read();
        }
    }
    sort(q+1,q+cq+1,cmp);
    int l=1,r=0,t=0;
    for(int i=1;i<=cq;i++)
    {
        int ql=q[i].l,qr=q[i].r,qt=q[i].tim;
        while(l<ql) del(l++);
        while(l>ql) add(--l);
        while(r<qr) add(++r);
        while(r>qr) del(r--);
        while(t<qt)
        {
            ++t;
            if(ql<=c[t].pos&&c[t].pos<=qr)
            {
                --cnt[num[c[t].pos]]; if(!cnt[num[c[t].pos]]) --sum;
                ++cnt[c[t].col]; if(cnt[c[t].col]==1) ++sum;
            }
            swap(num[c[t].pos],c[t].col);
        }
        while(t>qt)
        {
            if(ql<=c[t].pos&&c[t].pos<=qr)
            {
                --cnt[num[c[t].pos]]; if(!cnt[num[c[t].pos]]) --sum;
                ++cnt[c[t].col]; if(cnt[c[t].col]==1) ++sum;
            }
            swap(num[c[t].pos],c[t].col);
            --t;
        }
        ans[q[i].id]=sum;
    }
    for(int i=1;i<=cq;i++) printf("%d\n",ans[i]);
    return 0;
}
其他例题:
Luogu P2709 小B的询问
Luogu P3901 数列找不同
Luogu UVA12345 Dynamic len(set(a[L:R]))
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值