莫队算法

最近一段时间不知道学什么,索性就按着学长的课件查漏补缺,补充一下自己以前漏掉的知识点

莫队这个优秀的算法还是我们CJ学长莫涛队长发明的哩

普通莫队

先上道例题

给出一个长度为n 的数列,q次询问,每次询问一个区间中多少种不同的数字

这题显然可以用其他区间问题的数据结构来做,比如树状数组啥的
原题链接

但是!!!

为什么不 (问问神奇海螺呢) 试试暴力呢

让我们一起来分析一下暴力的写法:
每次遍历询问区间,统计出答案

那让我们想想,为什么这样的暴力跑不过呢?
因为询问的区间有很多重复的片段,导致某些点不断被遍历,大大减缓了效率

那我们是不是可以优化这个暴力???

当然

既然有很多重复的片段,那不是只要记录这个片段里保留的值就好了吗???
那我们该怎样在保证正确性的同时来记录这个值呢?

我们可以直接扩张或减小上一个询问区间来求得当前区间的值嘛,
这样就可以少算一部分重复的区间了

可是,这样的复杂度还是不够优秀,很容易被卡成O(N*Q)

想想为什么会被卡成O(N*Q)呢?

因为上一个询问区间和当前询问区间的两端点隔得太远,导致还是相当于几乎遍历了整个数组

所以

我们只要把上一个区间和当前区间的端点距离变近就好了!
所以把所有询问区间先排序 (这也是为什么普通莫队处理不了待修改的问题)
然后在按着询问来扫

那么我们剩下的问题就是在按照什么顺序来排序了

假如直接就按端点双关键字排序
那么在最坏的情况下就会: 左端点只遍历一遍数组,右端点却要每次询问都遍历一遍区间,那么又是O(N*Q)

所以我们不妨运用分块的思想, 将左端点按照所属的块来排序,右端点正常排序

// 也就是这样
int cmp(block a,block b)
{
    if(a.l/sqrt(n)==b.l/sqrt(n)) return a.r<b.r;
    return a.l<b.l; //已经保证了不在同一个块内,不用再除以sqrt(n)了
}

为什么这样复杂度是对的呢?

让我们分别考虑左右端点的移动情况:

左端点 : 每次跨越在一个块或者两个块,有Q次移动,就是O(Q*sqrt(N));

右端点 : 由于是在块内排序,所以每个块最多把整个序列遍历一遍,就是O(N*(sqrt(N)))

#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define get getchar()
#define in inline
in ll read()
{
    ll t=0; char ch=get;
    while(ch<'0' || ch>'9') ch=get;
    while(ch<='9' && ch>='0') t=t*10+ch-'0',ch=get;
    return t;
}
const int _=1e6+6;
int n;
ll m,a[_],piece,vis[_],ans=0,answer[_];
struct block{
    int l,r,id;
}q[_];
in int cmp(block a,block b)
{
    if(a.l/piece == b.l/piece)
        return a.r<b.r;
    return a.l<b.l;
}
in void del(int x)
{
    vis[x]--;
    if(vis[x]==0)ans--;
}
in void add(int x)
{
    if(vis[x]==0)ans++;
    vis[x]++;
}
int main()
{
    n=read();
    for(re int i=1;i<=n;i++)
        a[i]=read();
    m=read();
    for(re int i=1;i<=m;i++)
        q[i].l=read(),q[i].r=read(),q[i].id=i;
    piece=sqrt(n);
    sort(q+1,q+m+1,cmp);
    int l=1,r=0;
    for(re int i=1;i<=m;i++)
    {
        while(l<q[i].l) del(a[l]),l++;
        while(l>q[i].l) add(a[l - 1]),l--;
        while(r<q[i].r) add(a[r + 1]),r++;
        while(r>q[i].r) del(a[r]),r--;
        answer[q[i].id]=ans;
 	}
    for(re int i=1;i<=m;i++)
        cout<<answer[i]<<endl;
}

带修改莫队

又是一道例题

Luogu P1903 数颜色
给定一个长为 \(n\) 的序列,与m个操作,一种为单点修改,一种为询问区间中有多少个不同的数字,T组询问

思路

看到这个题面,想想可以用哪些方法来解决?
刚刚我们不是学了莫队吗,那我们想想如何用莫队来解决这个问题

首先

之前我们学到了,莫队只适合在无修改且可离线的情况下使用

那我们有什么办法在莫队中加入修改操作呢???

我们一起来想想普通莫队为什么不支持修改操作,
显然,是因为莫队需要排序,而排序之后修改操作的顺序就乱了.

然后

为何不记录一下每次询问前是第几个修改操作呢?

并且在排序时把这个 "时间戳" 作为第三关键字

in int cmp(qu a,qu b)
{
	if((a.l-1)/block==(b.l-1)/block) // block为莫队中每一个询问块的大小
	{
		if((a.r-1)/block==(b.r-1)/block)return a.t<b.t; // t为时间戳
		return a.r<b.r;
	}
	else return a.l<b.l;
}

每次询问都相应的更新或恢复之前的值

// 莫队单点修改函数
void change(int i,int l,int r,bool flag) //第i个修改操作,当前询问区间 L~R 
{     // flag为0表示当前位置需要修改为操作要求的值,为1表示需要恢复成此次操作之前的样子
	int x=modify[i].x;
	if(x>=l&&x<=r)
  		del(x); //把当前颜色对答案的影响删掉
	c[x]=flag?modify[i].now:modify[i].last;
	if(x>=l&&x<=r)
		add(x); //把修改后的颜色对答案的影响加入
}

附上此题完整代码 YZHX太胖了,常数也大,要开O2才能过:

#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
in int read()
{
    int t=0; char ch=get;
    while(ch<'0' || ch>'9') ch=get;
    while(ch<='9' && ch>='0') t=t*10+ch-'0',ch=get;
    return t;
}
const int _=1e5+5;
struct qu{
    int l,r,t,id;
}a[_];  // 询问操作
struct mo{
    int x,now,last;
}modify[_]; //修改操作, now是修改前的数字,last是修改后的,x记录修改位置
int n,m,sum[_*10],c1[_],c[_],qtot,rtot,block,ans,s[_];
in void add(int x) //加入
{
    sum[c[x]]++;
    if(sum[c[x]]==1)ans++;
}
in void del(int x) //删除
{
    sum[c[x]]--;
    if(sum[c[x]]==0)ans--;
}
in int cmp(qu a,qu b) //排序顺序 
{
    if((a.l-1)/block==(b.l-1)/block)
    {
        if((a.r-1)/block==(b.r-1)/block)return a.t<b.t;
        return a.r<b.r;
    }
    else return a.l<b.l;
}
in void change(int i,int l,int r,bool flag) //修改
{
    int x=modify[i].x;
    if(x>=l&&x<=r)
        del(x);
    c[x]=flag?modify[i].now:modify[i].last;
    if(x>=l&&x<=r)
        add(x);
}
int main()
{
    n=read(),m=read();
    block=sqrt(n);
    for(re int i=1;i<=n;i++)
        c1[i]=c[i]=read(); //需要 c1 数组记录序列初始状态
    for(re int i=1;i<=m;i++)
    {
        char ch=get;
        while(ch!='Q'&&ch!='R')ch=get;
        int x=read(),y=read();
        if(ch=='Q') a[++qtot].l=x,a[qtot].r=y,a[qtot].t=rtot,a[qtot].id=qtot;
        else modify[++rtot].x=x,modify[rtot].now=c[x],modify[rtot].last=y,c[x]=y;
    }
    for(re int i=1;i<=n;i++) c[i]=c1[i];
    sort(a+1,a+qtot+1,cmp);
    int l,r,tnow;
    l=a[1].l,r=a[1].r,tnow=a[1].t;
    for(re int i=l;i<=r;i++)
        add(i);
    for(re int i=1;i<=tnow;i++)
        change(i,l,r,0);
    s[a[1].id]=ans;
    for(re int i=2;i<=qtot;i++)
    {
        while (l<a[i].l) del(l++);
        while (l>a[i].l) add(--l);
        while (r<a[i].r) add(++r);
        while (r>a[i].r) del(r--);
        while (tnow<a[i].t) change(++tnow,l,r,0);
        while (tnow>a[i].t) change(tnow--,l,r,1);
        s[a[i].id]=ans;
    }
    for(re int i=1;i<=qtot;i++)
        cout<<s[i]<<endl;
}

posted @ 2019-07-24 10:50  yzhx  阅读(346)  评论(2编辑  收藏  举报