分块莫队学习笔记

news/2025/1/18 14:27:39/文章来源:https://www.cnblogs.com/zphh/p/18553906

优雅的暴力。

引入

link。

这道题显然可以用线段树、树状数组做,但如果我偏不用这些数据结构呢?

我们知道,暴力修改和查询最坏是 \(\mathcal{O}(n)\) 的,这样肯定会挂掉。

那该怎么办呢?

正题

分块

考虑将序列分成若干块,我们设每块长为 \(B\)

对于每次查询 \(\left [ l, r \right ]\),我们涉及到修改的块是 \(\left [ b_l, b_r \right ]\)\(b_i\) 代表 \(i\) 属于哪个块)。

其中 \(\left [ b_l + 1, b_r - 1 \right ]\) 是整块都被修改了。

不妨设置一个懒标记,把每块的整块操作都加到这里面。

这样修改的复杂度是 \(\mathcal{O}(\frac{n}{B})\) 的。

那剩下的我们就可以暴力操作,复杂度是 \(\mathcal{O}(B)\) 的。

查询同理。

此时修改查询的复杂度就变成了 \(\mathcal{O}(B + \frac{n}{B})\) 了。

使得该数最小的显然是 \(B = \sqrt{n}\),所以该算法的时间复杂度是 \(\mathcal{O}(m\sqrt{n})\)

分块主要解决区修区查类问题,只要满足以下条件即可:

  • 可以打懒标记(结合律)。
  • 时间复杂度允许。

优势:可解决问题范围广。

劣势:时间复杂度高。

时间复杂度:\(\mathcal{O}(m\sqrt{n})\)

空间复杂度:\(\mathcal{O}(n)\)

莫队

普通莫队

莫队是一种离线算法,需要满足以下条件:

  • 在知道 \(\left [ l, r \right ]\) 的答案的情况下,可以 \(\mathcal{O}(1)\) 求出 \(\left [ l, r + 1 \right ]\)\(\left [ l, r - 1 \right ]\)\(\left [ l + 1, r \right ]\)\(\left [ l - 1, r \right ]\) 的答案。
  • 允许离线。
  • 只有询问没有修改。

首先将所有的询问离线下来,记为 \(\left [ ql_1, qr_1 \right ],\left [ ql_2, qr_2 \right ],\dots,\left [ ql_m, qr_m \right ]\)

将询问排序(这正是莫队算法的精髓),从上一个询问的答案一个个改到当前询问,得到答案。

实现:

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] = res;
}

但是仔细分析发现时间复杂度仍然可以被卡成 \(nm\),一点都不优秀,甚至会更慢。

考虑优化

我们想要优化复杂度的根本是让 \(l\)\(r\) 指针移动的距离尽量少。

对询问范围进行分块,块长为 \(B\)

以询问左端点的块编号为第一关键字,右端点为第二关键字排序。

  • 如果当前询问与上一次处于同一块,则 \(l\) 最多移动 \(B\)
  • 不同块的询问,\(l\) 最多移动 \(2B\)

则:

  • \(l\) 移动的复杂度是 \(m\times B = mB\)
  • \(r\) 的复杂度是 \(\frac{n}{B} \times n = \frac{n^2}{B}\)

则复杂度是 \(\mathcal{O}(mB + \frac{n^2}{B})\)

使得该式最小的 \(B\) 的值是 \(\frac{n}{\sqrt m}\),则此时的时间复杂度就是 \(\mathcal{O}(n\sqrt{m} + m\log m)\)

\(m \log m\) 是排序的复杂度。

总结一下。

普通莫队解决的问题满足以下条件:

  • 在知道 \(\left [ l, r \right ]\) 的答案的情况下,可以 \(\mathcal{O}(1)\) 求出 \(\left [ l, r + 1 \right ]\)\(\left [ l, r - 1 \right ]\)\(\left [ l + 1, r \right ]\)\(\left [ l - 1, r \right ]\) 的答案。
  • 允许离线。
  • 只有询问没有修改。

优势:再没有更快的思维做法之前,她几乎是跑得最快并且思维含量最低的。

劣势:只支持离线。

时间复杂度: \(\mathcal{O}(n\sqrt{m} + m\log m)\)

空间复杂度: \(\mathcal{O}(n)\)

例题 1:小 B 的询问

非常板子的一道,维护一下 \(c\) 数组即可。

#include <bits/stdc++.h>
// #define int long long
#define pii pair<int, int>
#define FRE(x) freopen(x ".in", "r", stdin), freopen(x ".out", "w", stdout)
#define ALL(x) x.begin(), x.end()
using namespace std;int _test_ = 1;const int N = 50008;int n, m, k, block_size, res, cnt[N], a[N], ans[N];
struct node {int l, r, id;
} q[N];bool operator<(node x, node y) {int xl = (x.l - 1) / block_size + 1, xr = (x.r - 1) / block_size + 1;int yl = (y.l - 1) / block_size + 1, yr = (y.r - 1) / block_size + 1;return (xl != yl) ? (xl < yl) : (x.r < y.r);
}void add(int x) {res += cnt[a[x]] * 2 + 1;cnt[a[x]]++;
}void del(int x) {res -= cnt[a[x]] * 2 - 1;cnt[a[x]]--;
}void init() {}void clear() {}void solve() {cin >> n >> m >> k;for (int i = 1; i <= n; i++) {cin >> a[i];}block_size = n / sqrt(m); // 块长for (int i = 1; i <= m; i++) {cin >> q[i].l >> q[i].r;q[i].id = i;}sort(q + 1, q + m + 1);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] = res;}for (int i = 1; i <= m; i++) {cout << ans[i] << "\n";}
}signed main() {ios::sync_with_stdio(0);cin.tie(0), cout.tie(0);
//	cin >> _test_;init();while (_test_--) {clear();solve();}return 0;
}

不过此题块长就是 \(1\) 都能在 \(700\) 毫秒以内过,数据太水。

例题 2:小 Z 的袜子

也是非常板子的一道,维护一下 \(c\) 数组,并将上一题中的答案分别记分子分母即可。

请注意分子为 \(0\) 的情况。

#include <bits/stdc++.h>
// #define int long long
#define pii pair<int, int>
#define FRE(x) freopen(x ".in", "r", stdin), freopen(x ".out", "w", stdout)
#define ALL(x) x.begin(), x.end()
using namespace std;int _test_ = 1;const int N = 500008;int n, m, k, block_size, len;
pii res;
int cnt[N], a[N];
pii ans[N];
struct node {int l, r, id;
} q[N];bool operator<(node x, node y) {int xl = (x.l - 1) / block_size + 1, xr = (x.r - 1) / block_size + 1;int yl = (y.l - 1) / block_size + 1, yr = (y.r - 1) / block_size + 1;return (xl != yl) ? (xl < yl) : (x.r < y.r);
}void add(int x) {res.first += cnt[a[x]];res.second += len;len++;cnt[a[x]]++;
}void del(int x) {len--;cnt[a[x]]--;res.first -= cnt[a[x]];res.second -= len;
}void init() {}void clear() {}void solve() {cin >> n >> m;for (int i = 1; i <= n; i++) {cin >> a[i];}block_size = n / sqrt(m);for (int i = 1; i <= m; i++) {cin >> q[i].l >> q[i].r;q[i].id = i;}sort(q + 1, q + m + 1);int l = 1, r = 0;for (int i = 1; i <= m; i++) {if (q[i].l == q[i].r) ans[q[i].id] = {0, 1};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);if (res.first == 0) {ans[q[i].id] = {0, 1};continue;}int g = __gcd(res.first, res.second);ans[q[i].id] = {res.first / g, res.second / g};}for (int i = 1; i <= m; i++) {cout << ans[i].first << "/" << ans[i].second << "\n";}
}signed main() {ios::sync_with_stdio(0);cin.tie(0), cout.tie(0);
//	cin >> _test_;init();while (_test_--) {clear();solve();}return 0;
}

事实证明,还是 \(B = \frac{n}{\sqrt{m}}\) 跑得最快。

带修莫队

由于不能带修改实在是太别扭了,所以出现了带修莫队

带修莫队的思想跟所有可持久化数据结构是差不多的。

link.

由于加进了修改,我们无法再像正常莫队一样转移了。

可以考虑在迭代时增加一维时间戳。

每次就按顺序一个一个增加或减少修改即可。

同时就要以右端点所在块编号为第二关键字、时间为第三关键字排序。

时间复杂度与最优块长

设块长为 \(B\)、序列长度为 \(n\)、询问次数为 \(q\)、修改次数为 \(c\)

  • 左右端点移动上文分析过,是 \(qB + \frac{n^2}{B}\) 的。
  • 时间指针,对于每一个块,我们至多移动 \(c\) 次,即 \(\frac{n}{B} \times \frac{n}{B} \times c = \frac{cn^2}{B^2}\)

总时间复杂度为 \(\mathcal{O}(qB + \frac{n^2}{B} + \frac{cn^2}{B^2})\)

最优块长大概是……

\[\frac{n^2}{3^{1/3}(9m^3n^2+\sqrt{3}\sqrt{27m^6n^4-m^3n^6})^{1/3}}+\frac{(9m^3n^2+\sqrt{3}\sqrt{27m^6n^4-m^3n^6})^{1/3}}{3^{2/3}m} \]

所以还是取一个更好看一点的。

譬如 \(B = \sqrt[3]{n^2}\)

所以此时时间复杂度是约 \(\mathcal{O}(\sqrt[3]{n^5})\)


总结一下,带修莫队需要满足以下条件:

  • 在知道 \(\left [ l, r \right ]\) 的答案的情况下,可以 \(\mathcal{O}(1)\) 求出 \(\left [ l, r + 1 \right ]\)\(\left [ l, r - 1 \right ]\)\(\left [ l + 1, r \right ]\)\(\left [ l - 1, r \right ]\) 的答案。
  • 允许离线。

优势:可以允许修改。

劣势:比思维方法慢且只能离线。、

时间复杂度:\(\mathcal{O}(n\log n + \sqrt[3]{n^5})\)​。

空间复杂度: \(\mathcal{O}(n)\)

例题1:数颜色 / 维护队列

按上文中写的模拟即可。

#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define FRE(x) freopen(x ".in", "r", stdin), freopen(x ".out", "w", stdout)
#define ALL(x) x.begin(), x.end()
using namespace std;int _test_ = 1;const int N = 2e6 + 5; int n, m, block_size, cnt_c, cnt_q, a[N], bel[N], cnt[N], ans[N], res;
struct query {int l, r, t, id;
} c[N], q[N];
bool operator<(query x, query y) {return (bel[x.l] != bel[y.l]) ? (x.l < y.l) : ((bel[x.r] != bel[y.r]) ? (x.r < y.r) : (x.t < y.t));
}
void build() {block_size = pow(n, 0.666);for (int i = 1; i <= n; i++) {bel[i] = (i - 1) / block_size + 1;}
}
void add(int x) {res += (cnt[x] == 0);cnt[x]++;
}
void del(int x) {cnt[x]--;res -= (cnt[x] == 0);
}
void upt(int x, int y) {if (q[y].l <= c[x].l && c[x].l <= q[y].r) {del(a[c[x].l]);add(c[x].r);}swap(a[c[x].l], c[x].r);
}void init() {}void clear() {}void solve() {cin >> n >> m;for (int i = 1; i <= n; i++) {cin >> a[i];}build();for (int i = 1; i <= m; i++) {char op;int l, r;cin >> op >> l >> r;if (op == 'Q') q[++cnt_q] = {l, r, cnt_c, cnt_q};else c[++cnt_c] = {l, r, 0, 0};}sort(q + 1, q + cnt_q + 1);int l = 1, r = 0, t = 0;for (int i = 1; i <= cnt_q; i++) {while (l > q[i].l) add(a[--l]);while (r < q[i].r) add(a[++r]);while (l < q[i].l) del(a[l++]);while (r > q[i].r) del(a[r--]);while (t < q[i].t) upt(++t, i);while (t > q[i].t) upt(t--, i); ans[q[i].id] = res;}for (int i = 1; i <= cnt_q; i++) cout << ans[i] << "\n";
}signed main() {ios::sync_with_stdio(0);cin.tie(0), cout.tie(0);// cin >> _test_;init();while (_test_--) {clear();solve();}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/871246.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

day0java准备

Java-001 Markdown 暂时跳过,vscode里未能实现编译 java 1.已配置环境 2.继续学黑马程序员:已到50/200 3.内存4.学到方法(C里函数)

NB!一款基于java开发的漏洞检测工具,集合了泛微、用友、大华、海康、致远、红帆、万户、帆软等漏洞

1、工具介绍 基于 https://github.com/yhy0/ExpDemo-JavaFX 上添加poc 2、工具下载链接: 工具下载:工具下载 3、新增检测漏洞用友NC-Cloud系统接口getStaffInfo存在SQL注入漏洞 用友U8-Cloud ReleaseRepMngAction存在SQL注入漏洞复现(CNVD-2024-33023) 用友U8-CRM系统getDeptN…

网络购物数据分析

#获取数据 import pandas as pd infor=pd.read_csv(buy_input_1.csv) infor.head(20) #选取预观察数据#方法1 # x1=infor["Annual Income"] # print(x1) # x2=infor["Spending Score"] # print(x2)# """ #方法2 # x1=infor.iloc[:,3] # …

了解ESP32睡眠模式及其功耗

转载自:https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/ Insight Into ESP32 Sleep Modes & Their Power ConsumptionThe ESP32 is undeniably a worthy competitor to many WiFi/MCU SoCs, outperforming them in both performance and price. Ho…

某公交管理系统简易逻辑漏洞+SQL注入挖掘

某公交管理系统挖掘 SQL注入漏洞 前台通过给的账号密码,进去 按顺序依次点击1、2、3走一遍功能点,然后开启抓包点击4当点击上图的4步骤按钮时,会抓到图下数据包,将其转发到burp的重放模块构造以下注入poc,可见注入延时了五秒,用户输入的语句成功拼接到原有的SQL语句上执行…

记一次常规的网络安全渗透测试

前言 上个月根据领导安排,需要到本市一家电视台进行网络安全评估测试。通过对内外网进行渗透测试,网络和安全设备的使用和部署情况,以及网络安全规章流程出具安全评估报告。本文就是记录了这次安全评估测试中渗透测试部分的内容,而且客户这边刚刚做过了一次等保测评,算一下…

工具 | Hfish

0x00 简介 HFish是一款社区型免费蜜罐。 下载地址 HFish下载: HFish下载 0x01 功能说明支持多种蜜罐服务支持自定义Web蜜罐支持流量牵引支持端口扫描感知能力支持多种告警方式注:仅供安全研究与学习之用,若将工具做其他用途,由使用者承担全部法律及连带责任,作者及发布者不…

ida(持续更新)

如题前缀 说明sub_ 指令和子函数起点locret_ 返回指令loc_ 指令off_ 数据,包含偏移量seg_ 数据,包含段地址值asc_ 数据,ASCII字符串byte_ 数据,字节(或字节数组)word_ 数据,16位数据(或字数组)dword_ 数据,32位数据(或双字数组)qword_ 数据,64位数据(或4字数组)…

工具 | Hashcat

0x00 简介 Hashcat是一款强大的密码破解工具。 下载地址 Hashcat下载: Hashcat下载 0x01 功能说明直接破解组合攻击掩码暴力破解混合攻击联合攻击注:仅供安全研究与学习之用,若将工具做其他用途,由使用者承担全部法律及连带责任,作者及发布者不承担任何法律及连带责任。

深入理解主键和外键:数据库设计的基石

title: 深入理解主键和外键:数据库设计的基石 date: 2025/1/18 updated: 2025/1/18 author: cmdragon excerpt: 在现代信息系统中,数据的管理和存储是至关重要的。关系数据库作为一种广泛使用的数据存储方式,其设计的合理性直接影响到数据的完整性和系统的性能。在关系数据…

一次性讲清如何合理搭配一台组装电脑

今天这篇文章我就给大家一次性讲清,我们如何合理搭配组装一台电脑,新手小白建议收藏观看,希望对大家有所帮助。 1 .首先要知道组装一台电脑, 以下八大硬件缺一不可: 2. 认识【CPU】: CPU品牌: 目前市场主流消费级CPU只有英特尔与AMD。 英特尔CPU: intel的CPU分为i3、i5、i…

ADCP414、ADCP416四通道125MSPS速率ADC替代AD9653、AD9253,可提供ZZKK证明

ADCP416-125/105/80是一款4通道、16位、125/105/80MSPS模数转换器(ADC),内置片内采样保持电路,专门针对低成本、低功耗、小尺寸和易用性而设计。该产品的转换速率最高可达125MSPS,具有杰出的动态性能与低功耗特性,适合比较重视小封装尺寸的应用。ADCP416-125特性和优势 --电…