首页 > 其他分享 >莫队 学习笔记

莫队 学习笔记

时间:2023-06-26 20:46:27浏览次数:34  
标签:复杂度 笔记 times 学习 端点 区间 莫队 size

莫队 学习笔记

引入

莫队算法是一种优美的暴力算法,可以用来处理大量的区间询问。前提是,维护的信息可以高效地插入、删除。

我们就以一道题为例,来初探莫队:洛谷P3901 数列找不同

题意:给定一个数列,\(q\) 次询问,问区间 \([l, r]\) 内的数是否各不相同。

首先,我们很容易想到,问某个区间内的数各不相同,等价于去问这个区间的长度是否等于其中不同数的个数。那对于一个询问,我们先考虑暴力做:拿一个桶维护一下区间内每个数的个数,以及区间内不同种数的个数,从区间左侧扫到区间右侧,求出答案。但是,如果暴力去做,最后复杂度是 $q \times n $ 的,因为可能每次询问都要把整个数列扫描一遍。

我们又发现,其是我们并不需要每次都从头扫,因为有些区间的信息是重复的,那我们就要多利用重复的信息,于是就有了第二个思路:把询问离线下来,按左端点为第一关键字,右端点为第二关键字排序,用双指针来维护元素的插入和删除。这样,左端点的数就可以保证利用充分,但是,如果左端点各不相同,右端点还是会左右横跳,复杂度还是逃不过 \(q \times n\)。

现在问题就变为,如何进行排序,来让每次左右端点尽量少移动,于是就有了莫队。

思想

莫队把区间分块,把询问以左端点所在的块为第一关键字,右端点为第二关键字进行排序,让我们来看一看这样做后的复杂度:

我们设块长为 \(size\)。首先,对于每个块内的询问,每次询问后左端点最多移动 \(size\) 次,那么所有询问后左端点移动的次数就是 \(q \times size\);然后我们来考察右端点。我们发现,在每个块内,右端点的移动都是单调不减的,可以做到线性;对于两个块之间的转换,右端点最多从最右侧移动到最左侧,也就是说,右端点最多移动 \(n \times \frac{n}{size}\) 次。又因为 \(n\) 和 \(q\) 同阶,所以最终,莫队的复杂度就是 \(O(n \times size + n\times \frac{n}{size})\)。

根据基本不懂不等式,我们发现当 \(size = \sqrt{n}\) 时,复杂度最小。所以按照 \(\sqrt{n}\) 分块,复杂度就变成 \(O(n \sqrt{n})\) 了。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;

inline int read(){
    int x = 0; char ch = getchar();
    while(ch<'0' || ch>'9') ch = getchar();
    while(ch>='0'&&ch<='9') x = x*10+ch-48, ch = getchar();
    return x;
}
int bel[N];
struct Question{
    int l, r, id;
    int part;
    bool operator <(const Question &b) const{
        if(part == b.part){
            if(part & 1) return r<b.r;//玄学优化,按照奇偶性分别对右端点进行升/降序排序,让每次右端点回退最少。
            else return r>b.r;
        } else return part<b.part;
    }
}q[N];
bool ans[N];
int a[N], n;
int Q;
int l = 0, r = 0;

int vis[N], tot;
void del(int pos){
    vis[a[pos]]--;
    if(!vis[a[pos]]) tot--;
}
void add(int pos){
    if(!vis[a[pos]]) tot++;
    vis[a[pos]]++;
}

int main(){
    n = read(), Q = read();
    int lth = sqrt(n);
    for(int i = 1; i<=n; ++i) a[i] = read();
    for(int i = 1; i<=Q; ++i){
        q[i].l = read(), q[i].r = read();
        q[i].id = i;
        q[i].part = ((q[i].l-1)/lth)+1;
    }
    sort(q+1, q+Q+1);
    for(int i = 1; i<=Q; ++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] = (tot == (r-l+1));
    }
    for(int i = 1; i<=Q; ++i){
        ans[i]?puts("Yes"):puts("No");
    }
    return 0;
}

标签:复杂度,笔记,times,学习,端点,区间,莫队,size
From: https://www.cnblogs.com/frostwood/p/17506648.html

相关文章

  • Java学习——MarkDown语法学习
    MarkDown基础学习——一级标题(#加空格)二级标题(##加空格)三级标题(###加空格)四级标题(####加空格)...直到六级标题字体hello,word!——粗体两边加2星号hello,word!——斜体两边加1星号hello,word!——斜体加粗两边加3星号hello,word!——废弃两边加2波浪线引用路漫漫......
  • 学习爬虫入门3,正则表达式,代码复现
    正则表达式写回调函数def (self,response) ......
  • Freertos学习03-Task优先级
    一、前言FreeRTOS是一个流行的实时操作系统,它允许用户创建多个任务并在它们之间共享处理器时间。在FreeRTOS中,任务的优先级别是非常重要的,因为它决定了任务在系统中的执行顺序。二、任务优先级特点FreeRTOS中的任务优先级别是一个整数,范围从0到configMAX_PRIORITIES-1,其......
  • Kong入门学习实践(5)API网关路由转发
    最近在学习Kong网关,因此根据老习惯,我会将我的学习过程记录下来,一来体系化整理,二来作为笔记供将来翻看。由于我司会直接使用Kong企业版,学习过程中我会使用Kong开源版。本篇,我们学习快速配置一个最常见的基本功能:API网关场景下的路由转发。API网关路由需求在API网关的需求场景中,......
  • 【js学习笔记八】如何写一个简单的前端回调函数
     目录前言导语代码部分 运行结果总结前言我是歌谣我有个兄弟巅峰的时候排名c站总榜19叫前端小歌谣曾经我花了三年的时间创作了他现在我要用五年的时间超越他今天又是接近兄弟的一天人生难免坎坷大不了从头再来歌谣的意志是永恒的放弃很容易但是坚持一定很酷导语书写一......
  • 【js学习笔记九】前端异步请求逐步进行一回调
     目录前言导语前言运行结果解决方案运行结果前言我是歌谣我有个兄弟巅峰的时候排名c站总榜19叫前端小歌谣曾经我花了三年的时间创作了他现在我要用五年的时间超越他今天又是接近兄弟的一天人生难免坎坷大不了从头再来歌谣的意志是永恒的放弃很容易但是坚持一定很酷导语......
  • FIO的再学习-不同Raid,不同磁盘性能验证
    FIO的再学习-不同Raid性能验证背景发现自己对iodepth的和num_jobs的理解存在偏差找了一些资料才发现自己很多地方做的不对.这里找到一个新资料可以进行模拟数据库的测试测试配置文件#R/W:8/2#ThreadPoolNum:64#IOThreadNum:32[global]runtime=120time_b......
  • python代码-基于深度强化学习的微能源网能量管理与优化策略研究
    python代码-基于深度强化学习的微能源网能量管理与优化策略研究关键词:微能源网;能量管理;深度强化学习;Q-learning;DQN内容::面向多种可再生能源接入的微能源网,提出一种基于深度强化学习的微能源网能量管理与优化方法。该方法使用深度Q网络(deepQnetwork,DQN)对预测负荷、风光等可......
  • 学习C++就这么简单 ——《写给大家看的C++书》
    C++已经有很多年的历史了,虽然在它之后又出现了Java和C#之类的新语言,但它至今仍是人们开发软件时的最佳选择之一。那些巨头中的巨头,如微软、Adobe、英特尔、亚马逊、Google、苹果、诺基亚等公司,都在使用C++。这门语言相对比较容易使用(选用本书作为入门教材就更是如此了......
  • Linux用户管理笔记1
    useradd创建用户命令: useraddwork    #创建名为work的一般用户以及用户所属组,用来日常完成工作的用户,普通用户下不能够新建普通用户。 useradd-rwork #创建名为work的系统用户以及所属组群,默认情况下不能登录服务器,只能去调用某个服务程序......