莫队学习笔记和题目集

莫队

莫队算法一般分为两类

  • 莫队维护区间答案
  • 维护区间内的数据结构
  • 树上莫队,带修改莫队、二维莫队等等

普通莫队

  • 将询问离线排序处理,使转移的次数尽量少
  • 基于分块思想优化
    • 若在其 l 在同块,那么将其 r 作为排序关键字
    • l 不在同块,就将 l 作为关键字排序

对于n与m同阶,一般可以设块长度为 n \sqrt{n} n

复杂度

l l l 的移动在一个 b l o c k block block内,复杂度平摊为 O ( m ∗ b l o c k ) O(m*block) O(mblock)

r r r的移动对于同一个块的 l l l最多移动 n n n 次,复杂度平摊为 O ( n ∗ n / b o l c k ) O(n*n/bolck) O(nn/bolck)


排序方式

常用

将序列分成 n ∗ n \sqrt{n}*n n n 个长度为 n \sqrt{n} n 的块,若左端点在同一个块内,则按右端点排序

(以左端点所在块为第一关键字,右端点为第二关键字)

struct Node{
	int left;
    int right;
	int id;			//左右端点和id
	friend bool operator <(const Node& a, const Node& b) {
		return (a.left / block == b.left / block ? 	//若在同一个块,右端点从小到大
                a.right < b.right : a.left < b.left);	//左端点从小到大
	}
}q[maxn];

奇偶性优化

指针移到右边后不用再跳回左边,而跳回左边后处理下一个块又要跳回右边

这样能减少一半操作,理论上能快一倍

struct Node{
	int left;
	int right;
	int id;
	friend bool operator <(const Node& a, const Node& b) {
		return belong[a.left] ^ belong[b.left] ? belong[a.left] < belong[b.left] :
        											//若两者分块不同,从小到打排序
			belong[a.left] & 1 ? a.right<b.right : a.right>b.right;
        	//按块的奇偶性,从左到有和从右到左
	}
}q[maxn];

分块大小分析

分块时块的大小不是固定的,要根据题目具体分析(往往自以为被卡常,其实分块不对)

分析的过程以下方的过程为例

我们设块长度为 $block $

那么对于任意多个在同一块内的询问,挪动的距离就是 n n n

n b l o c k \frac{n}{block} blockn 个块,移动的总次数就是 n 2 b l o c k \frac{n^2}{block} blockn2

移动可能跨越块,所以还要加上一个 m ∗ b l o c k m*block mblock 的复杂度,总复杂度为 O ( n 2 b l o c k + m ∗ b l o c k ) O(\frac{n^2}{block}+m*block) O(blockn2+mblock)

我们要让这个值尽量小, b l o c k block block n m \frac{n}{\sqrt{m}} m n是最优的

复杂度为 O ( n 2 n m + m ( n m ) ) = O ( n m ) O(\frac{n^2}{\frac{n}{\sqrt{m}}}+m(\frac{n}{\sqrt{m}}))=O(n\sqrt{m}) O(m nn2+m(m n))=O(nm )


转移

for (int i = 1; i <= m; i++) {
		while (stdl > q[i].left) {
			stdl--;
			//operation 左端添加
		}
		while (stdr < q[i].right) {
			stdr++;
			//operation 右端添加
		}
		while (stdl < q[i].left) {
			//operation 左端删除
			stdl++;
		}
		while (stdr > q[i].right) {
			//operation 右端删除
			stdr--;
		}
		res[q[i].id] = ans;
	}

模板题

P1494 小Z的袜子

HDU 6534 Chika and Friendly Pairs


题意

a b s ( A [ i ] − A [ j ] ) ≤ k abs(A[i]-A[j]) \leq k abs(A[i]A[j])k

则称 A [ i ] A[i] A[i] A [ j ] A[j] A[j]为一对好数

给出 a [ ] a[] a[]数组, m m m组询问

返回 l l l r r r区间内好数数量

思路

  • 加入一个点,对答案产生的贡献为已知区间内好数的个数,可以树状数组求(下面讲)

    可以离线--------------->莫队

  • 树状数组 + 离散化能够维护区间 x − k ≤ v a l ≤ x + k x-k \leq val \leq x+k xkvalx+k的个数

    • 每次加入一个节点,即为单点修改
    • a [ ] a[] a[]排序后,然后对每个 a [ i ] a[i] a[i]二分, a [ i ] − k 、 a [ i ] + k a[i]-k、a[i]+k a[i]ka[i]+k的区间,每次只要查询固定区间权值树状数组求和即可

    如此实现 l o g ( n ) log(n) log(n)的修改查询

  • 莫队维护左右转移

    n , m n,m n,m同阶,分块取 n \sqrt{n} n 即可

    随手再加个奇偶性优化

代码

树状数组
inline int lowbit(int x) {
	return x & -x;
}
void modify(int x, int val) {	//修改函数,1添加节点,-1删除节点
	while (x <= n) {
		tree[x] += val;
		x += lowbit(x);
	}
}
int query(int x) {	//求和函数
	int res = 0;
	while (x) {
		res += tree[x];
		x -= lowbit(x);
	}
	return res;
}
离散化
for (int i = 1; i <= n; i++) { 
		a[i].data = io.read();
		a[i].id = i;	
		p[i] = a[i];	//初始化权值
	}
	sort(p + 1, p + 1 + n);	//排序,离散化
	for (int i = 1; i <= n; i++)a[p[i].id].id = i;//返回每个数再权值数组中位置
二分预处理区间
void question(int id) {
	int x = a[id].data;
    //找到第一个大于等于x-k的数
	int left = 1, right = n, mid;		//初始化二分
	int stdl, stdr, limit = max(0, x - k);
	while (left <= right) {
		mid = (left + right) >> 1;
		if (p[mid].data >= limit)right = mid - 1, stdl = mid;
        //若符合条件,使val尽量小
		else left = mid + 1;
	}
    //找到最后一个小于x+k的数
	left = 1, right = n, limit = x + k;
	while (left <= right) {
		mid = (left + right) >> 1;
		if (p[mid].data <= limit)left = mid + 1, stdr = mid;
        //若符合条件,使val尽量大
		else right = mid - 1;
	}
	l[id] = stdl; r[id] = stdr;
}
莫队排序
struct Node{
	int left;
	int right;
	int id;
	friend bool operator <(const Node& a, const Node& b) {//奇偶性优化排序
		return belong[a.left] ^ belong[b.left] ? belong[a.left] < belong[b.left] :
			belong[a.left] & 1 ? a.right<b.right : a.right>b.right;
	}
}q[maxn]; 
莫队
for (int i = 1; i <= m; i++) {	//离线询问
		q[i].left = io.read();
		q[i].right = io.read();
		q[i].id = i;
	}
sort(q + 1, q + 1 + m);	//分块排序
int stdl = 1, stdr = 0; LL ans = 0;
for (int i = 1; i <= m; i++) {
	while (stdl > q[i].left) {
		stdl--;
		modify(a[stdl].id, 1);//添加左端点
		ans = ans + query(r[stdl]) - query(l[stdl] - 1) - 1;//增加好数
	}
	while (stdr < q[i].right) {
		stdr++;
		modify(a[stdr].id, 1);//添加右端点
		ans = ans + query(r[stdr]) - query(l[stdr] - 1) - 1;//增加好数
	}
	while (stdl < q[i].left) {
		ans = ans - (query(r[stdl]) - query(l[stdl] - 1) - 1);//删除好数
		modify(a[stdl].id, -1);//删除左端点
		stdl++;
	}
	while (stdr > q[i].right) {
		ans = ans - (query(r[stdr]) - query(l[stdr] - 1) - 1);//删除好数
		modify(a[stdr].id, -1);//删除右端点
		stdr--;
	}
	res[q[i].id] = ans;
}
AC
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long LL;
const int maxn = 50005;
class QIO {
public:
	char buf[1 << 21], * p1 = buf, * p2 = buf;
	int getc() {
		return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
	}
	int read() {
		int ret = 0, f = 0;
		char ch = getc();
		while (!isdigit(ch)) {
			if (ch == '-')
				f = 1;
			ch = getc();
		}
		while (isdigit(ch)) {
			ret = ret * 10 + ch - 48;
			ch = getc();
		}
		return f ? -ret : ret;
	}
} io;
int  n, k, m;
struct Data{
	int data;
	int id;
	friend bool operator <(const Data& a, const Data& b) {
		return a.data < b.data;
	}
}a[maxn], p[maxn];
int tree[maxn];
inline int lowbit(int x) {
	return x & -x;
}
void modify(int x, int val) {
	while (x <= n) {
		tree[x] += val;
		x += lowbit(x);
	}
}
int query(int x) {
	int res = 0;
	while (x) {
		res += tree[x];
		x -= lowbit(x);
	}
	return res;
}
int l[maxn], r[maxn];
void question(int id) {
	int x = a[id].data;
	int left = 1, right = n, mid;
	int stdl, stdr, limit = max(0, x - k);
	while (left <= right) {
		mid = (left + right) >> 1;
		if (p[mid].data >= limit)right = mid - 1, stdl = mid;
		else left = mid + 1;
	}
	left = 1, right = n, limit = x + k;
	while (left <= right) {
		mid = (left + right) >> 1;
		if (p[mid].data <= limit)left = mid + 1, stdr = mid;
		else right = mid - 1;
	}
	l[id] = stdl; r[id] = stdr;
}
int block, belong[maxn];
struct Node{
	int left;
	int right;
	int id;
	friend bool operator <(const Node& a, const Node& b) {
		return belong[a.left] ^ belong[b.left] ? belong[a.left] < belong[b.left] :
			belong[a.left] & 1 ? a.right<b.right : a.right>b.right;
	}
}q[maxn]; 
LL res[maxn];
int main() {
	n = io.read();
	m = io.read();
	k = io.read();
	block = sqrt(n);
	for (int i = 1; i <= n; i++) { 
		a[i].data = io.read();
		a[i].id = i;
		p[i] = a[i];
		belong[i] = i / block;
	}
	sort(p + 1, p + 1 + n);
	for (int i = 1; i <= n; i++)a[p[i].id].id = i;
	for (int i = 1; i <= n; i++)question(i);
	for (int i = 1; i <= m; i++) {
		q[i].left = io.read();
		q[i].right = io.read();
		q[i].id = i;
	}
	sort(q + 1, q + 1 + m);
	int stdl = 1, stdr = 0; LL ans = 0;
	for (int i = 1; i <= m; i++) {
		while (stdl > q[i].left) {
			stdl--;
			modify(a[stdl].id, 1);
			ans = ans + query(r[stdl]) - query(l[stdl] - 1) - 1;
		}
		while (stdr < q[i].right) {
			stdr++;
			modify(a[stdr].id, 1);
			ans = ans + query(r[stdr]) - query(l[stdr] - 1) - 1;
		}
		while (stdl < q[i].left) {
			ans = ans - (query(r[stdl]) - query(l[stdl] - 1) - 1);
			modify(a[stdl].id, -1);
			stdl++;
		}
		while (stdr > q[i].right) {
			ans = ans - (query(r[stdr]) - query(l[stdr] - 1) - 1);
			modify(a[stdr].id, -1);
			stdr--;
		}
		res[q[i].id] = ans;
	}
	for (int i = 1; i <= m; i++)
		printf("%lld\n", res[i]);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值