首页 > 其他分享 >初识莫队

初识莫队

时间:2024-03-29 13:37:28浏览次数:29  
标签:le int 询问 ++ -- 初识 莫队

莫队

个人理解:
这是一种较为暴力的算法,适用于解决维护序列区间操作的问题。

主要思想:

  • 把所有的操作离线,按某种方式重新排序。
  • 操作过程中不断转移当前区间的答案。
  • (\([L, R]\Rightarrow [L \pm 1, R \pm 1]\))
  • 希望转移的复杂度尽量的小(\(O(n\sqrt{m})\))

01-普通的莫队(无修改)

01-例题:P3901 数列找不同

01-题意

现有数列 \(A_1,A_2,\ldots,A_N\),\(Q\) 个询问 \((L_i,R_i)\),询问 \(A_{L_i} ,A_{L_i+1},\ldots,A_{R_i}\) 是否互不相同。

对每个询问输出一行,YesNo

\(1 \le N,Q \le 10^5\),\(1 \le A_i \le N\),\(1 \le L_i \le R_i \le N\)。

02-分析

考虑维护两个指针lr,对于每次询问 \([L, R]\) ,把 lr移动到 \([L,R]\),在移动的过程中不断统计不同的数的个数tot,如果 tot != r - l + 1,那么答案就为No,反之Yes

cin >> n >> q;
for (int i = 1; i <= n; i++)
    cin >> a[i];
    
auto add = [&](int x){ if (cnt[x] == 0) tot++; cnt[x]++; };
auto del = [&](int x) {if (cnt[x] == 1) tot--; cnt[x]--; };
    
for (int l = 0, r = 0, L, R; q--; ){
    cin >> L >> R;
    while(l < L) del(a[l++]); // l L R
    while(r < R) add(a[++r]); // L r R
    while(l > L) add(a[--l]); // L l R
    while(r > R) del(a[r--]); // L R r
    cout << (tot == r - l + 1 ? "Yes" : "No") << endl;
}

显而易见的是,这并不能通过本题

瓶颈在于每次询问都移动了lr,这使得时间复杂度来到了 \(O(qn)\),无法接受。

有没有一种方法,让这两个指针动的快一点?(每次移动的距离尽可能小)

发现有以下性质:

  • 询问的处理顺序与给出的顺序无关。
  • 没了。

这启发我们可以通过改变询问的顺序,使得lr更快的到达新的 \([L,R]\) 。

考虑先离线所有的询问,进行一个根号分块。

对于储存询问的两个结构体a, b:

首先判断a.l / B == b.l / B,即是否在同一块。

若在同一块,则按r大小排序,a.r < b.r

反之,则按l排序,a.l > b.l

struct node {
        int l, r, id;
} q[N];

for (int i = 1; i <= m; i++)
        cin >> q[i].l >> q[i].r, q[i].id = i;

int B = n / sqrt(m);

sort(q + 1, q + 1 + m, [](node a, node b) {
        return a.l / B == b.l / B ? a,r < b.r : a.l < b.l;
}); // 再也不需要cmp了

按照排序后的顺序操作,这样就使得复杂度降到了 \(O(n \sqrt{m})\) 。 足以通过本题。

02-例题:P1972 HH的项链 (LOJ)

  • (莫队只能过LOJ的数据,洛谷加强了)

01-题意

HH 有一串由各种漂亮的贝壳组成的项链。HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。HH 不断地收集新的贝壳,因此,他的项链变得越来越长。

有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?这个问题很难回答…… 因为项链实在是太长了。于是,他只好求助睿智的你,来解决这个问题。

一行一个正整数 \(n\),表示项链长度。
第二行 \(n\) 个正整数 \(a_i\),表示项链中第 \(i\) 个贝壳的种类。

第三行一个整数 \(m\),表示 HH 询问的个数。
接下来 \(m\) 行,每行两个整数 \(l,r\),表示询问的区间。

对于 \(100\%\) 的数据,\(1\le n,m,a_i \leq 10^6\),\(1\le l \le r \le n\)。

02-分析

与上一题一样,做法差不多,只是问的东西不一样了。
注意莫队只能过LOJ,洛谷的数据加强了,要用树桩数组

03-例题:P2709 小B的询问

01-题意

小B 有一个长为 \(n\) 的整数序列 \(a\),值域为 \([1,k]\)。
他一共有 \(m\) 个询问,每个询问给定一个区间 \([l,r]\),求:

\[\sum\limits_{i=1}^k c_i^2 \]

其中 \(c_i\) 表示数字 \(i\) 在 \([l,r]\) 中的出现次数。

对于 \(100\%\) 的数据,\(1\le n,m,k \le 5\times 10^4\)。

02-分析

与上一题一致,依题意模拟即可
需要推柿子。

04-例题 :P1494 小 Z 的袜子

01-题意

作为一个生活散漫的人,小 Z 每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小 Z 再也无法忍受这恼人的找袜子过程,于是他决定听天由命……

具体来说,小 Z 把这 \(N\) 只袜子从 \(1\) 到 \(N\) 编号,然后从编号 \(L\) 到 \(R\) 的袜子中随机选出两只来穿。尽管小 Z 并不在意两只袜子是不是完整的一双,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。

你的任务便是告诉小 Z,他有多大的概率抽到两只颜色相同的袜子。当然,小 Z 希望这个概率尽量高,所以他可能会询问多个 \((L,R)\) 以方便自己选择。

然而数据中有 \(L=R\) 的情况,请特判这种情况,输出0/1

输入文件第一行包含两个正整数 \(N\) 和 \(M\)。\(N\) 为袜子的数量,\(M\) 为小 Z 所提的询问的数量。接下来一行包含 \(N\) 个正整数 \(C_i\),其中 \(C_i\) 表示第 \(i\) 只袜子的颜色,相同的颜色用相同的数字表示。再接下来 \(M\) 行,每行两个正整数 \(L, R\) 表示一个询问。

包含 \(M\) 行,对于每个询问在一行中输出分数 \(A/B\) 表示从该询问的区间 \([L,R]\) 中随机抽出两只袜子颜色相同的概率。若该概率为 \(0\) 则输出 0/1,否则输出的 \(A/B\) 必须为最简分数。

\(100\%\) 的数据中,\(N,M \leq 50000\),\(1 \leq L < R \leq N\),\(C_i \leq N\)。

02-分析

同样维护袜子的种类数,注意同时维护用来算概率的组合数。

05-例题 P7764 Poklon

统计区间内出现两次的数,依题意模拟即可。

02-带修莫队

注意到刚刚的例题中都没有修改的情况,莫队同样能处理修改,但更加复杂,不过本质相同。

P1903 数颜色 / 维护队列

01-题意

墨墨购买了一套 \(N\) 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会向你发布如下指令:

  1. \(Q\ L\ R\) 代表询问你从第 \(L\) 支画笔到第 \(R\) 支画笔中共有几种不同颜色的画笔。

  2. \(R\ P\ C\) 把第 \(P\) 支画笔替换为颜色 \(C\)。

为了满足墨墨的要求,你知道你需要干什么了吗?


第 \(1\) 行两个整数 \(N\),\(M\),分别代表初始画笔的数量以及墨墨会做的事情的个数。

第 \(2\) 行 \(N\) 个整数,分别代表初始画笔排中第 \(i\) 支画笔的颜色。

第 \(3\) 行到第 \(2+M\) 行,每行分别代表墨墨会做的一件事情,格式见题干部分。

对于每一个 Query 的询问,你需要在对应的行中给出一个数字,代表第 \(L\) 支画笔到第 \(R\) 支画笔中共有几种不同颜色的画笔。


对于所有数据,\(n,m \leq 133333\)

所有的输入数据中出现的所有整数均大于等于 \(1\) 且不超过 \(10^6\)。

先写到这里吧
2023年12月5日17:22:22

02-分析

在原来的基础上增加了时间这一个维度,如果按照原来的排序方式,显而易见会得出错误的答案。

如果不想抛弃莫队的思路,如何保证答案的正确性?

既然多了时间这一维度,不妨在转移lr的同时转移一个时间变量t

  • 如果当前的t小于询问的t,那么upt(++t)
  • 如果当前的t大于询问的t,那么upt(t--)

像这样操作,就可以保证时间也对的上了。

注意代码细节:

  • 如果在转移t的过程中对[l,r]中的元素进行了修改,那么需要更新当前的答案。
  • l, r, t的转移顺序建议是 l -> l'r -> r't -> t'
  • 分开记录操作序列与询问序列,调整块的大小B = n / pow(m, 0.666)
  • 询问的排序方式需要改变:
a.l / B == b.l / B ? 
(a.r / B == b.r / B ? a.lst < b.lst : a.r < b.r) 
: a.l < b.l
#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 5;

int n, m, l, r, t, B;
int qcnt, ccnt, ans;
int a[N], res[N], cnt[N];

char op;

struct node {
    int l, r, lst, id;
} q[N], c[N];

void add(int x, int f = 0) {
    x = f ? x : a[x];
    ans += !cnt[x]++;
}

void del(int x, int f = 0) {
    x = f ? x : a[x];
    ans -= !--cnt[x];
}

void upt(int k, int x) {
    int L = q[k].l, R = q[k].r, idx = c[x].l, &to = c[x].r;

    if (L <= idx && idx <= R) {
        del(idx);
        add(to, 1);
    }

    swap(a[idx], to);
}

signed main() {

    ios::sync_with_stdio(0);
    cin.tie(0);

    cin >> n >> m;
    B = pow(n, 0.666);

    for (int i = 1; i <= n; i++)
        cin >> a[i];

    for (int i = 1, x, y; i <= m; i++) {
        cin >> op >> x >> y;

        if (op == 'Q') q[++qcnt] = {x, y, ccnt, qcnt};
        else c[++ccnt] = {x, y, 0, 0};
    }

    sort(q + 1, q + 1 + qcnt, [](node a, node b) {
        return a.l / B == b.l / B ? (a.r / B == b.r / B ? a.lst < b.lst : a.r < b.r) : a.l < b.l;
    });
    l = 1;

    for (int i = 1; i <= qcnt; i++) {
        while (l < q[i].l) del(l++);

        while (l > q[i].l) add(--l);

        while (r < q[i].r) add(++r);

        while (r > q[i].r) del(r--);

        while (t < q[i].lst) upt(i, ++t);

        while (t > q[i].lst) upt(i, t--);

        res[q[i].id] = ans;
    }

    for (int i = 1; i <= qcnt; i++)
        cout << res[i] << endl;

    return 0;
}

莫队进阶用法

03-回滚莫队

04-P5906 最远距离

题目描述

给定一个序列,多次询问一段区间 \([l,r]\),求区间中相同的数的最远间隔距离

序列中两个元素的间隔距离指的是两个元素下标差的绝对值

数据范围

之后 \(m\) 行,每行两个整数 \(l,r\) 表示询问区间。

对于 \(100\%\) 的数据,满足 \(1\leq n,m\leq 2\cdot 10^5\),\(1\leq a_i\leq 2\cdot 10^9\)。

分析

对于这样的题目,单单只是普通的莫队难以下手。

添加操作比较简单,而删除操作十分困难。

既然困难,那就不删除了呗

这时候,可以用回滚莫队解决,在做法上与普通莫队稍有不同:

  • 对询问按照左端点分块排序,左端点在相同块的询问按照右端点递增排序。
  • 依次处理每一个块内的所有询问。
  • 对于同一个块的每一个询问,让指针 l 从块的最右边开始移动到对应的位置。
  • 而对于右指针r,则继承同一块内上一个询问的的位置与答案。
  • 每处理完一个询问,将l指针挪回最左边,路上消除每个位置的影响。

时间复杂度 \(O(n\sqrt n\ +\) 巨大的常数 \()\) 。

记得离散化和不要用太多的memset

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 5;

int n, m, l, r, ans;
int a[N], b[N], res[N];

int cntL[N], cntR[N], cntL_[N], cntR_[N];

int B, tot;
int kuai[N], L[N], R[N];

int used_[N], top_;

struct node{
    int l, r, id;
}q[N];

inline void cmin(int &a, int b) { a = min(a, b); }
inline void cmax(int &a, int b) { a = max(a, b); }

void add(int x, int f = 0){
    int p = a[x];
    if (f) cmin(cntL[p], x);
    cmax(cntR[p], x), cmax(ans, cntR[p] - min(cntL[p], x));
}



signed main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    
    cin >> n; B = sqrt(n); tot = n / B;
    
    for (int i = 1; i <= n; i++)
        cin >> a[i], b[i] = a[i];
    
    cin >> m;
    
    for (int i = 1; i <= m; i++)
        cin >> q[i].l >> q[i].r, q[i].id = i;
    
    sort(b + 1, b + 1 + n);
    
    int len = unique(b + 1, b + 1 + n) - b - 1;
    
    for (int i = 1; i <= n; i++)
        a[i] = lower_bound(b + 1, b + 1 + len, a[i]) - b,
        kuai[i] = (i - 1) / B + 1;
    
    for (int i = 1; i <= tot; i++)
        L[i] = R[i - 1] + 1, R[i] = L[i] + B - 1;
    
    if (R[tot] < n) ++tot, L[tot] = R[tot - 1] + 1, R[tot] = n;
    
    sort(q + 1, q + 1 + m, [](node a, node b){
        return kuai[a.l] == kuai[b.l] ? a.r < b.r : a.l < b.l;
    });
    
    memset(cntL, 0x3f, sizeof cntL), memset(cntR, 0, sizeof cntR);
    memset(cntL_, 0x3f ,sizeof cntL_), memset(cntR_, 0, sizeof cntR_);
    
    for (int i = 1, num = 1; i <= tot; i++){
        
        memset(cntL, 0x3f, sizeof cntL), memset(cntR, 0, sizeof cntR);
//        while(top) cntL[used[top]] = 0x3f3f3f3f, cntR[used[top]] = 0, top--;
        for (l = R[i] + 1, r = R[i], ans = 0; kuai[q[num].l] == i; num++){
            if (kuai[q[num].l] == kuai[q[num].r]){
                while(top_) cntL_[used_[top_]] = 0x3f3f3f3f, cntR_[used_[top_]] = 0, top_--;
                for (int k = q[num].l; k <= q[num].r; k++) used_[++top_] = a[k], cmin(cntL_[a[k]], k), cmax(cntR_[a[k]], k);
                for (int k = q[num].l; k <= q[num].r; k++) cmax(res[q[num].id], cntR_[a[k]] - cntL_[a[k]]);
                continue;
            }
            
            while(r < q[num].r) add(++r, 1);
            int now = ans;
            while(l > q[num].l) add(--l);
            
            res[q[num].id] = ans, ans = now;
            
            while(l < R[i] + 1) {
                if (cntR[a[l]] == l) cntR[a[l]] = 0;
                l++;
            }
        }
    }
    
    for (int i = 1; i <= m; i++)
        cout << res[i] << endl;
    
    
    return 0;
}

Written with StackEdit中文版.

标签:le,int,询问,++,--,初识,莫队
From: https://www.cnblogs.com/genshin-player/p/18103669

相关文章

  • 初识HTML(二)
    列表作用:布局内容排列整齐的区域。列表分类:无序列表、有序列表、定义列表。无序列表作用:布局排列整齐的不需要规定顺序的区域。标签:ul嵌套li,ul是无序列表,li是列表条目。<ul><li>第一项</li><li>第二项</li><li>第三项</li></ul>效果:  注:ul标签......
  • 初识HTML(一)
    什么是HTML?HTML是一种超文本标记语言。超文本(链接)标记(带尖括号的标签)标签语法-标签都是成对出现的,中间可以包裹内容-<>里面放标签的名字,一般都是小写-结束标签多一个/-标签还可以分为单标签、双标签单标签<inputtype="text">双标签<strong>加粗</strong>H......
  • 爬虫初识
    爬虫初识【一】爬虫介绍【1】定义爬虫是一种自动化获取互联网数据的技术,通过模拟浏览器行为,向目标网站发送请求并获取响应,然后解析响应中的数据。应用领域:爬虫可以应用于各种网站数据的获取【2】工作原理爬虫通过发送HTTP请求,模拟浏览器行为,获取网站的响应,并解析响应中的......
  • OpenCV初识
    1、OpenCV简介OpenCV(OpenSourceComputerVisionLibrary)是一个开源的计算机视觉和机器学习软件库。它由一系列的C函数和少量C++类构成,同时提供Python、Java和MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。#导入OpenCV库importcv2#打印OpenCV......
  • flutter--day1 初识
    flutter&dart初体验前言:我平时只使用VUE和uniapp,最近的一个项目是一个pad上的app,于是了解了下移动端。现在比较流行的两个框架flutter和reactnative,刚好项目进入尾声,就来研究一下flutter吧!地址dart中文网:https://dart.cn/guidesflutter中文网:https://flutter.cn/docs......
  • 莫队
    以为自己一辈子接触不到的算法,本来以为很高深,没想到是优雅的暴力,太绝妙了对于多个区间查询,例如区间最大值等,我们考虑暴力,枚举区间$[L,R]$,取最大值即可,时间复杂度$O(m*(R-L))$,跑不起,所以我们借用数据结构,单调队列,树状数组等等,但是如果此时我们考虑优化暴露首先我......
  • 就业班 第二阶段 2401--3.26 day6 Shell初识 连接vscode
    远程连接vs_code可能出现的问题C:\Users\41703\.ssh验证远程主机的身份,如果连不上vscode,可以尝试删除这里面的公钥代码。重新安装那个扩展,排除扩展本身的问题谁连过我,并操作了什么curlhttps://gitea.beyourself.org.cn/newrain001/shell-project/raw/branch/master......
  • 鸿蒙操作系统-初识
    HarmonyOS-初识简述安装配置helloworld1.创建项目2.目录解释3.构建页面4.真机运行应用程序包共享包HARHSP快速修复包官方文档请参考:HarmonyOS简述1.定义:HarmonyOS是分布式操作系统,它旨在为不同类型的智能设备提供统一的操作系统,可以在手机、平板、智能穿戴、智......
  • 1 初识C语言
    1.1C语言的起源C语言是一种通用的高级语言,最初是由丹尼斯·里奇在贝尔实验室为开发UNIX操作系统而设计的。C语言最开始是于1972年在DECPDP-11计算机上被首次实现。1.2C语言的特点易于学习。结构化语言。它产生高效率的程序。它可以处理底层的活动。它可......
  • 1-1 初识C51单片机的各种准备工作
    大家好呀,这里是小X。相信大家已经准备好进行51单片机的开发了。但对于现在的我们来说,人来了,魂没来。魂来了,电脑没准备好。当我们满怀信心的把咱们的开发板连到电脑上,熟悉的提示音响起,然后——没了。对,是这么离谱。就像找网课要去小破站,上百度要戳小红书(不是广子),想要进行......