莫队算法

啥是莫队算法??

莫队算法其实本质就是暴力。

但是莫队算法在暴力的时候,规划好了每一次暴力的顺序,统筹安排暴力,可以有效地降低总时间。

怎么做呢?

首先看一道例题。

P3901 数列找不同

问题要我们判断每个区间里面是不是每个数都不一样。

假设你是一个刚学会编程的人,不会任何数据结构,你会怎么办呢?

有一种比较简单的思路是开一个桶,然后每次询问就清空桶,再把区间装进去,最后遍历一遍桶,看看有没有哪个桶里是有超过$1$个的。

Anyway,我们也可以遍历一遍桶,统计出一共的不同种类的数,如果种类数等于$r-l+1$就说明每个数都不一样。

 

这样我们就很接近莫队算法的思想了。

1.相邻区间转移

现在考虑,我们已经统计出了$[l,r]$,如何知道$[l,r+1]$的种类数。

我们已经有了$[l,r]$中每种数的个数,我们把$a[r+1]$加入,如果发现$cnt[a[r+1]]$是空的,我们种类数$++$,然后$cnt[a[r+1]]++$

这样子我们就能从$[l,r]$转移到$[l,r+1]$了。

这种转移是$O(1)$的,同理,剩下几个边界加加减减都很好推出来。

1 void add(int x){
2     if(!cnt[a[x]])now++;
3     cnt[a[x]]++;
4 }
5 void del(int x){
6     --cnt[a[x]];
7     if(!cnt[a[x]])now--;
8 }
View Code

我们也可以通过位运算压一下位

1 void add(int x){now+=!cnt[a[x]]++;}
2 void del(int x){now-=!--cnt[a[x]];}
View Code

这个代码可以直接插在主函数里面,可以加快不少的时间。

2.询问排序

通过区间转移,我们可以很方便地用上一个询问求出的数据去求出下一个询问了。

但是,如果两个询问之间距离很大,程序还是$O(nm)$的。

我们可以离线回答询问,这样子就可以通过统筹回答,降低时间了。

一个比较容易想到的方法是按照左端点的大小排序。

但是这样子只要区间的大小交替变换就能卡掉了。

比如说下面这组数据

$[1,1000],[2,3],[3,1000],[4,4]$

明显按照左端点排序是行不通的。

 

我们可以按照块给左端点排序,左端点在同一个块内时右端点升序。

只要分块合理,在块内移动所需要的时间是很小的,也就是说我们给左端点一定的容错性。

在一定的范围内,左端点可以来回移动,这样子既保证了左端点的复杂度,右端点的复杂度也不会上天。

 

在随机数据下,块的大小为$\frac{n}{m\times \frac{2}{2}}$时,时间复杂度比较优秀。

 

我们排序之后跑暴力就十分快了。

完整代码:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N=5e5+1009;
 4 struct Q{
 5     int l,r,id;
 6     Q(int aa=0,int bb=0,int cc=0){l=aa;r=bb;id=cc;}
 7 }q[N];
 8 int read(){
 9     char c;int num,f=1;
10     while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
11     while(c=getchar(), isdigit(c))num=num*10+c-'0';
12     return f*num;
13 }
14 int n,m,a[N],block,ans[N];
15 int cnt[N],now=0;
16 bool cmp(Q a,Q b){
17     return (a.l/block==b.l/block)?a.r<b.r:a.l/block<b.l/block;
18 }
19 void add(int x){now+=!cnt[a[x]]++;}
20 void del(int x){now-=!--cnt[a[x]];}
21 int main()
22 {
23     n=read();m=read();
24     for(int i=1;i<=n;i++)a[i]=read();
25     for(int j=1;j<=m;j++){
26         q[j].id=j;
27         q[j].l=read();
28         q[j].r=read();
29     }
30     block=n/sqrt(m*2/3);
31     sort(q+1,q+1+m,cmp);
32     int nl=1,nr=1;
33     add(1); 
34     for(int i=1;i<=m;i++){
35         int l=q[i].l,r=q[i].r;
36         while(nr<r)add(++nr);
37         while(nr>r)del(nr--);
38         while(nl<l)del(nl++);
39         while(nl>l)add(--nl);
40         ans[q[i].id]=(now==(r-l+1));
41     }
42     for(int i=1;i<=m;i++)
43         printf("%s\n",ans[i]?"Yes":"No");
44     return 0;
45 }
View Code


这里有一个邪门优化,可以让复杂度除2。

我们在按左端点分块排序的基础上,对奇偶编号的块交替升降序对右端点排序。

也就是说奇数的时候右端点升序,偶数的时候右端点降序。

这样可以玄学降低时间复杂度。

为什么可以这么做呢,假设我们一开始升序,排序完之后,右端点在最右边。如果新的块内还是升序的话,我们就要先从右跑到左,然后再跑回去,这样是很慢的。

为何不从右跑的左的时候顺便统计了呢?

这样子,原来要跑两趟,现在只需要一趟就可以了,对时间的优化其实是很大的。

时间复杂度为$O(n\sqrt{m})$

带修莫队和树上莫队留坑待补。。

转载于:https://www.cnblogs.com/onglublog/p/10158669.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值