莫队算法

莫队算法的原理讲解,就不多做解释,网上一大片

学习资料推荐:

https://www.luogu.org/blog/codesonic/Mosalgorithm

莫涛大神的知乎

分块思想和时间复杂度

这篇文章的分块思想(如何让L和R这两个指针变得更少)和普通莫队的时间复杂度证明不是很好。

在上篇文章的时候我们提到用分块的思想来优化:对于两个询问,若在其l在同块,那么将其r作为排序关键字,若l不在同块,就将l作为关键字排序(这就是双关键字)

 

也就是说这个序列我们分成了\sqrt{n}块,每一块都x个询问,且这x个询问的右端点是递增的,然后我们先对这一块查找计算。

这样就可以优化时间复杂度么,我们看一下严格的证明(摘自大米饼的博客):

首先,枚举m个答案,就一个m了。设分块大小为unitl

下面给出证明:

我们分类讨论:

①l的移动:若下一个询问与当前询问的l所在的块不同,那么只需要经过最多2*unit步可以使得l成功到达目标.复杂度为:O(m*unit)

②r的移动:r只有在Be[l]相同时才会有序(其余时候还是疯狂地乱跳,你知道,一提到乱跳,那么每一次最坏就要跳n次!),Be[l]什么时候相同?在同一块里面l就Be[]相同。对于每一个块,排序执行了第二关键字:r。所以这里面的r是单调递增的,所以枚举完一个块,r最多移动n次。总共有n/unit个块:复杂度为:O(n*n/unit)

总结:O(n*unit+n*n/unit)(n,m同级,就统一使用n)

根据基本不等式得:当unit为sqrt(n)时,得到莫队算法的真正复杂度:

O(n*sqrt(n))

优化:

快读快写就不说了(很快的)

奇偶性排序

学习文章还提到了:奇偶性排序

这样能快是因为右指针移到右边后不用再跳回左边,而跳回左边后处理下一个块又要跳回右边,这样能减少一半操作,理论上能快一倍

代码:



//return pos[a.l]<pos[b.l]||(pos[a.l]==pos[b.l]&&(pos[a.l]&1?a.r<b.r:a.r>b.r));///奇偶排序

分块的优化

sz=n/sqrt(m*2/3);

 

普通莫队模板

///莫队算法模板:求区间不同数的个数
#include<cstdio>
#include<iostream>
#include<fstream>
#include<algorithm>
#include<functional>
#include<cstring>
#include<string>
#include<cstdlib>
#include<iomanip>
#include<numeric>
#include<cctype>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<list>
#include<set>
#include<map>
using namespace std;
const int maxn=1000005;
typedef long long ll;
int n,m,k;
//莫队算法主要处理离线问题,查询只给出L,R
//当[L,R]很容易向[L-1,R],[L+1,R],[L,R-1],[L,R+1]转移时可用莫队
//注意转移的时候先扩张再收缩,L先向右,L再向左,最后再收缩
//add就是当前区间添加某元素时要做的操作
//del就是当前区间删除某元素时要做的操作
//add,del函数写的时候都要注意结构顺序
struct node
{
    int l,r,id;
}Q[maxn]; ///保存询问值
int pos[maxn];///保存所在块
bool cmp(const node &a,const node &b)
{
   return pos[a.l]<pos[b.l]||(pos[a.l]==pos[b.l]&&(pos[a.l]&1?a.r<b.r:a.r>b.r));///奇偶排序
   //if(pos[a.l]==pos[b.l]) return a.r<b.r;//先按l所在的块排,如果相等就按r排
    //else return pos[a.l]<pos[b.l];
}
ll gcd_(ll a,ll b)
{
	return b==0?a:gcd_(b,a%b);
}
int num[maxn*2];///保存区间的个数
int a[maxn];

ll ans[maxn],ans2[maxn];
int L=0,R=0;
ll Ans=0;
void add(int x)
{
	num[a[x]]++; 
	if(num[a[x]]==1) Ans++;
}
void del(int x)
{
	
	 num[a[x]]--;
	 if(num[a[x]]==0) Ans--;
}
int main()
{      int T;
    scanf("%d",&T);
    int cas=0;
    while(T--)
    {
	memset(num,0,sizeof(num));
	cas++;
	scanf("%d%d",&n,&m);
	int sz=n/sqrt(m*2/3*1.0);
	//int sz=sqrt(n*1.0);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        pos[i]=i/sz;
    }
    
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&Q[i].l,&Q[i].r);
        Q[i].id=i;
    }
    sort(Q+1,Q+1+m,cmp);
	num[0]=0;
    L=1;R=0;Ans=0;
    for(int i=1;i<=m;i++) 
    {
        while(R<Q[i].r)
        {
            R++;
            add(R);
        }
        while(L>Q[i].l) ///前缀和L+1>Q[i].l
        {
            L--;
            add(L);
        }
        while(L<Q[i].l)///前缀和L+1<Q[i].l
        {
            del(L);
            L++;
        }
        while(R>Q[i].r)
        {
            del(R);
            R--;
        }
        ans[Q[i].id]=Ans;
    }
    printf("Case %d:\n",cas);
     for(int i=1;i<=m;i++)
        printf("%lld\n",ans[i]);
    }
    return 0;
}

带修改的莫队


///带莫队算法模板:求区间不同数的个数+单点修改
#include<cstdio>
#include<iostream>
#include<fstream>
#include<algorithm>
#include<functional>
#include<cstring>
#include<string>
#include<cstdlib>
#include<iomanip>
#include<numeric>
#include<cctype>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<list>
#include<set>
#include<map>
using namespace std;
const int maxn=1000005;
typedef long long ll;

void add(int x);
void del(int x);
void modify(int x,int ti);  //这个函数会执行或回退修改ti(执行还是回退取决于是否执行过,具体通过swap实现),x表明当前的询问是x,即若修改了区间[q[x].l,q[x].r]便要更新答案

int sz,cnt[1000010],a[50010],Ans,ans[50010];

struct Change
{
    int p,col;
}c[50010];

struct Query
{
    int l,r,t,id;
    bool operator<(Query& b)
    {
        return l/sz==b.l/sz?(r/sz==b.r/sz?t<b.t:r<b.r):l<b.l;
    }
}q[50010];

int main()
{
     int n,m;
    
    while(scanf("%d%d",&n,&m)!=-1)
    {
    memset(cnt,0,sizeof(cnt));	
	sz=pow(n,0.66666);
        
    for (int i=1;i<=n;i++)
    {
       scanf("%d",&a[i]);
    }
    char op[10];
    int ccnt=0,qcnt=0;
    for (int i=1;i<=m;++i)
    {
        scanf("%s",op);
        if (op[0]=='Q')
        {
            ++qcnt;
            scanf("%d%d",&q[qcnt].l,&q[qcnt].r) ;
            q[qcnt].t=ccnt;
            q[qcnt].id=qcnt;
        }
        else
        {
            ++ccnt;
            scanf("%d%d",&c[ccnt].p,&c[ccnt].col);
        }
    }
    
    sort(q+1,q+qcnt+1);
    int l=1,r=0,now=0;
    Ans=0;
    for (int i=1;i<=qcnt;++i)
    {
       
        
        while (r<q[i].r)
        {
            add(a[++r]);
        }
       while(l>q[i].l)
        {
            add(a[--l]);
        }
        while (l<q[i].l)
        {
            del(a[l++]);
        }
        while (r>q[i].r)
        {
            del(a[r--]);
        }
        while (now<q[i].t)
        {
            modify(i,++now);
        }
        while (now>q[i].t)
        {
            modify(i,now--);
        }
        
        ans[q[i].id]=Ans;
    }
    
     for (int i=1;i<=qcnt;++i)
     {
        cout<<ans[i]<<endl;
     }
    }
    
    return 0;
}

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

void del(int x)
{
	cnt[x]--;
    if (cnt[x]==0)
    {
        --Ans;
    }
}

void modify(int x,int ti)
{
    if (c[ti].p>=q[x].l&&c[ti].p<=q[x].r)
    {
        del(a[c[ti].p]);
        add(c[ti].col);
    }
    swap(a[c[ti].p],c[ti].col); 
}

例题:

BZOJ 2038 [2009国家集训队]小Z的袜子(hose)】    (莫队算法+数学公式)

Codeforces617 E . XOR and Favorite Number            (莫队算法+异或)

1188 - Fast Queries                                                       (莫队算法)

NBUT 1457 Sona                                                        (莫队算法+离散化)

Codeforces - 220B Little Elephant and Array             (莫队模板题+离散化)

codeforces 86D. Powerful array                                   (莫队算法)

P1903 [国家集训队]数颜色 / 维护队列                      (带修莫队算法模板:求区间不同数的个数+单点修改)

codeforces 940f Machine Learning                               【 带修改莫队+离散化】

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
算法是一种基于分块的算法,用于解决一些静态区间查询问题,时间复杂度为 $O(n\sqrt{n})$。以下是一个基于Python的算法的示例代码: ```python import math # 定义块的大小 BLOCK_SIZE = 0 # 初始化块的大小 def init_block_size(n): global BLOCK_SIZE BLOCK_SIZE = int(math.sqrt(n)) # 定义查询操作 def query(left, right): pass # 在这里写查询操作的代码 # 定义添加操作 def add(x): pass # 在这里写添加操作的代码 # 定义删除操作 def remove(x): pass # 在这里写删除操作的代码 # 定义算法 def mo_algorithm(n, q, queries): init_block_size(n) queries.sort(key=lambda x: (x[0] // BLOCK_SIZE, x[1])) left, right = 0, -1 for query in queries: while left > query[0]: left -= 1 add(left) while right < query[1]: right += 1 add(right) while left < query[0]: remove(left) left += 1 while right > query[1]: remove(right) right -= 1 query(query[0], query[1]) ``` 在这段代码中,我们首先定义了一个全局变量 `BLOCK_SIZE`,用于表示块的大小。接着,我们定义了三个操作函数 `query()`、`add()` 和 `remove()`,分别用于查询、添加和删除元素。在 `mo_algorithm()` 函数中,我们首先调用 `init_block_size()` 函数初始化块的大小,然后将查询操作按照块的大小和右端点排序,接着使用双指针维护当前查询区间的左右端点,每次移动指针时调用 `add()` 和 `remove()` 函数更新块的状态,最后调用 `query()` 函数进行查询操作。 请注意,这段代码只是一个示例,具体的 `query()`、`add()` 和 `remove()` 函数的实现取决于具体的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值