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

分块莫队学习笔记

时间:2024-11-18 22:40:53浏览次数:1  
标签:right 分块 int res 复杂度 笔记 mathcal 莫队 left

优雅的暴力。

引入

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}}\) 跑得最快。

(咕咕咕。。。)

标签:right,分块,int,res,复杂度,笔记,mathcal,莫队,left
From: https://www.cnblogs.com/zphh/p/18553906

相关文章

  • 读《Effective Java》笔记 - 条目3
    条目3:利用私有构造器或枚举类型强化Singleton属性Singleton是什么?是指只能实例化一次的类。Singleton通常用于表示无状态的对象,函数,或本质上唯一的系统组件。将一个类设计为Singleton会使其客户端测试变得十分困难,因为Singleton不能被继承,我们无法创建一个用来代替它的......
  • NFLS 字符串题单笔记(未完结)
    POI2010Antisymmetry对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。manacher板子,写就完了#include<bits/stdc++.......
  • 运维系列:Docker学习笔记(3)-- 如何使用Dockerfile构建镜像
    Docker学习笔记(3)--如何使用Dockerfile构建镜像Docker学习笔记(3)--如何使用Dockerfile构建镜像1.Dockerfile的书写规则及指令使用方法(1)FROM(指定基础image)该指令有两种格式:(2)MAINTAINER(用来指定镜像创建者信息)格式:(3)RUN(安装软件用)该指令有两种格式:......
  • NFLS DP题单笔记(做不动了未完结)
    录制唱片你刚刚继承了流行的“破锣摇滚”乐队录制的尚未发表的\(N\)(\(1\leqN\leq20\))首歌的版权。你打算从中精选一些歌曲,发行\(M\)(\(1\leqM\leq20\))张CD。每一张CD最多可以容纳\(T\)(\(1\leqT\leq20\))分钟的音乐,一首歌不能分装在两张CD中。CD数量可以用完,也可以......
  • NFLS 图论题单笔记(完结)
    John的农场是一张N*N的方格图,贝茜住在左上角(1,1),John住在右下角(N,N)。现在贝茜要去拜访John,每次都只能往四周与之相邻的方格走,并且每走一步消耗时间T。同时贝茜每走三步就要停下来在当前方格吃草,在每个方格吃草的用时是固定的,为H[i][j]。John想知道贝茜最少要多久才能到达Joh......
  • MySQL笔记
    数据类型整型createtabletest( atinyintunsigned,bint(6)unsignedzerofill)engine=innodbint(N)无论N是多少,int永远只占四个字节,N表示宽度,设置zerofill后不足的地方0补位数据类型字节数带符号最小值带符号最大值不带符号最小值不带符号最大值T......
  • 来自笔记本的移植
    编译汇编代码到可执行文件并执行步骤假设文件名字是flag.asm在linux中,先nasm-felfflag.asm-oflag.o然后再ld-melf_i386-oflagflag.o然后就可以了,找个时间看看,nasm的用法修改aslr参数值:sudosysctl-wkernel.randomize_va_space=0#这是修改为0p.sendline(shellcod......
  • 笔记本(2)
    汇编代码的一些前置知识:;立即寻址方式moveax,11;将11赋值给eaxaddeax,114504;eax加上114504subeax,1;eax减去1;寄存器寻址方式movebx,0x36d;将0x36d赋值给ebxmovedx,ebx;将ebx的值赋值给edx;直接寻址方式movec......
  • 笔记本(3)
    我记得当初有写这个在某个地方啊==找不到了...就是一些二进制文件知识和exp中一些代码在libc中,就开始很常见的使用elf=ELF('/pwn')write_plt=elf.plt['write']......got.....got.........这两是用来查询[]内函数的plt和got的地址并赋值给左边变量,还有相似的main=elf.sym['mai......
  • Express的使用笔记8 引入验证中间件来给表单添加验证规则~
    前面已经将数据成功写入了数据库了,接下来就开始探讨接口传递参数的校验咯~自己封装虽然灵活,但也常常架不住有现成的,既灵活又方便,比如:express-valiation官方文档地址:https://express-validator.github.io/docs/guides/schema-validation先安装咯!npmiexpress-valiation引入......