Martian148's blog Martian148's blog
首页
  • ICPC 算法笔记
  • ICPC 算法题解
  • 体系结构
  • 高等数学
  • 线性代数
  • 概率论与数理统计
  • 具体数学
  • Martian148的奇思妙想
  • 游记
  • 通识课笔记
关于
  • useful 网站
  • 友情链接
  • 分类
  • 归档

Martian148

一只热爱文科的理科生
首页
  • ICPC 算法笔记
  • ICPC 算法题解
  • 体系结构
  • 高等数学
  • 线性代数
  • 概率论与数理统计
  • 具体数学
  • Martian148的奇思妙想
  • 游记
  • 通识课笔记
关于
  • useful 网站
  • 友情链接
  • 分类
  • 归档
  • 线上赛板子(实时更新)
  • 数据结构

  • 数学

  • 计算几何

  • 动态规划

  • 图论

  • 字符串

  • 杂项

    • 01分数规划
    • 莫队
      • 引入
        • 暴力法几何解释
      • 莫队算法
        • 莫队算法的几何解释
      • 带修改的莫队
    • CDQ分治
    • 离散化
    • RMQ
    • 悬线法
    • 模拟退火
    • 整体二分
    • Blog 对拍 Debug 复杂度
  • 算法笔记
  • 杂项
martian148
2024-09-03
目录

莫队

# 莫队

转载大佬的博客 (opens new window)

# 引入

当如果知道区间 [L,R][L,R][L,R] 的答案,如果能 O(1)O(1)O(1) 地求出 [L−1,R],[L,R−1]⋯[L-1,R],[L,R-1] \cdots[L−1,R],[L,R−1]⋯ 相邻的区域的答案,那么,就可以考虑莫队

莫队算法 === 离线 +++ 暴力 +++ 分块

莫队时一种离线算法,他通常用于不修改只查询的一类区间问题,复杂度为 O(nn)O(n\sqrt{n})O(nn​) ,没有在线算法线段树或树状数组好,但是胜在好写

思考一个问题,

给定一个序列,和 mmm 组询问,每次询问 [L,R][L,R][L,R] 区间内不同的数有几个

考虑暴力做法,我们定义两个指针 L,RL,RL,R ,假设我们已经知道了 [L,R][L,R][L,R]的答案,我们可以平移指针来 O(1)O(1)O(1) 得到下一位置的答案,例如 [L+1,R],[L,R+1],⋯[L+1,R],[L,R+1],\cdots[L+1,R],[L,R+1],⋯

我们可以从 L=1,R=1L=1,R=1L=1,R=1 开始平移 nnn 次来得到任意区间 [L,R][L,R][L,R] 的答案,对于多组询问,我们可以把询问按照左结点排序,就相当于右节点一直反复跑,左节点向右移

显然,我们可以造一组恶心的数据把这样的想法卡死,每次右节点跑 nnn 次,mmm 组询问,总的时间复杂度为 O(nm)O(nm)O(nm),那么有没有什么办法能找一个询问的排序方案使得时间复杂度更优秀呢?答案是有的

# 暴力法几何解释

我们考虑用几何来解释区间和区间之间的连线,我们把区间 L,RL,RL,R 定义成坐标轴上的点 x=L,y=Rx=L,y=Rx=L,y=R ,两个区间之间的距离就是对应的两个点之间的距离的哈夫曼距离

image.png

(a)(a)(a) 情况中的区间是 (2,6)⇒(4,8)(2,6)\Rightarrow (4,8)(2,6)⇒(4,8) ,(b)(b)(b) 情况中的区间是 (2,9)⇒(3,5)(2,9)\Rightarrow (3,5)(2,9)⇒(3,5)

# 莫队算法

把数组分块,然后把查询的区间按左端点所在的块排序,如果左端点相同,再按右端点排序

# 莫队算法的几何解释

莫队算法把 xxx 轴分成了多个块,每个区块内按照 yyy 坐标排序,一个区块处理完了再进入下一个区块,这样就形成了一条比较短的路径,从而降低了时间复杂度

image.png

考虑 xxx 方向上的复杂度,在一个区块内,沿着 xxx 方向一次移动最多 n\sqrt{n}n​ 个单位,区块共有 mmm 个,总时间复杂度为 O(mn)O(m\sqrt{n})O(mn​)

考虑 yyy 方向上的时间复杂度,每个区块内,验证 yyy 方向单向移动,整个区块内的 yyy 方向长度为 nnn,有 n\sqrt{n}n​ 个区块,总时间复杂度为 nnn\sqrt{n}nn​

两者相加,总时间复杂度为 O(mn+nn)O(m\sqrt{n}+n\sqrt{n})O(mn​+nn​) 当 m,nm,nm,n 同阶时,复杂度为 O(nn)O(n\sqrt{n})O(nn​)

#include<bits/stdc++.h>
using namespace std;
const int MAXX=1e6;
struct node{
    int L,R,id;
    node(int L=0,int R=0,int id=0):L(L),R(R),id(id){}
};

int now_ans=0;
vector<int> a,belong,ans,cnt;
vector<node> q;

bool cmp(node a,node b){
    if(belong[a.L]!=belong[b.L]) return belong[a.L]<belong[b.L];
    return a.R<b.R;
}

void add(int x){cnt[a[x]]++;if(cnt[a[x]]==1) now_ans++;}
void del(int x){cnt[a[x]]--;if(cnt[a[x]]==0) now_ans--;}

int main(){
    freopen("P1972.in","r",stdin);
    freopen("P1972.out","w",stdout);
    int n;scanf("%d",&n);
    int block=sqrt(n),t=n/block+(n%block!=0);
    a.assign(n+1,0);belong.assign(n+1,0);ans.assign(n+1,0);cnt.assign(MAXX+1,0); 
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),belong[i]=(i-1)/block+1;
    int m;scanf("%d",&m);q.assign(m+1,node());
    for(int i=1;i<=m;i++){
        int L,R;scanf("%d%d",&L,&R);
        q[i]=node(L,R,i);
    }
    sort(q.begin()+1,q.begin()+1+m,cmp);
    int L=1,R=0;
    for(int i=1;i<=m;i++){
        while(L<q[i].L) del(L++);
        while(R>q[i].R) del(R--);
        while(L>q[i].L) add(--L);
        while(R<q[i].R) add(++R);
        ans[q[i].id]=now_ans;
    }
    for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

P1972P1972P1972 这道题数据莫队时过不了的www

莫队可以有些小优化,就是对于编号为奇数的块按照 RRR 从小到大排序,编号为偶数的块按照 RRR 从大到小表示

这个优化在图上也很显然能表示出来,从奇数点 yyy 坐标从小到大,然后偶数的 yyy 坐标从大到小,这样走能省掉一个块走完然后立马从 yyy 的高点到 yyy 的低点的过程

# 带修改的莫队

如果上面那个问题再添加一个修改操作

R x val 表示把第 xxx 位置上的数改成 valvalval

我们可以按照修改把询问 mmm 拆成几部分,然后一部分给一个时间维度 ttt

不修改的是二维平面上的点 (x,y)(x,y)(x,y) ,那么加上时间维度就是三维平面上一个点 (x,y,t)(x,y,t)(x,y,t)

image.png

就变成了在三维平面上选一条路径使得曼哈顿距离尽量小

依旧采用分块的办法,先按照左端点 LLL 排序,左端点 LLL 在同一个块,按照右端点排序,如果右端点在同一个块,就按照之间维度 ttt 排序

那么块的大小取多少最好呢,不妨设块的大小为 BBB

  1. xxx 方向上的复杂度。 在一个区块内,沿着 xxx 方向一次最多移动 BBB,所有的区块公用 mmm 次移动,总计算量为 mBmBmB
  2. yyy 方向上的复杂度。和 xxx 一样,总复杂度为 mBmBmB
  3. zzz 方向上的复杂度,,每个被 xxx 和 yyy 区块限制的方格内,沿着 zzz 方向单向移动最多 mmm 次(询问的次数)总共有 n2B2\frac{n^2}{B^2}B2n2​ 个方格,总计算量为 mn2/B2mn^2/B^2mn2/B2

总时间复杂度为 O(mB+mn2/B2)O(mB+mn^2/B^2)O(mB+mn2/B2) 。当 B=n23B=n^{\frac{2}{3}}B=n32​ 时有较好的复杂度 O(mn23)O(mn^{\frac{2}{3}})O(mn32​)

上次更新: 2025/04/08, 18:03:31
01分数规划
CDQ分治

← 01分数规划 CDQ分治→

最近更新
01
Java基础语法
05-26
02
开发环境配置
05-26
03
pink 老师 JavaScript 学习笔记
05-26
更多文章>
Theme by Vdoing | Copyright © 2024-2025 Martian148 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式