莫队学习笔记和题目集

莫队

莫队算法一般分为两类

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

普通莫队

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

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

复杂度

ll 的移动在一个blockblock内,复杂度平摊为O(mblock)O(m*block)

rr的移动对于同一个块的ll最多移动 nn 次,复杂度平摊为O(nn/bolck)O(n*n/bolck)


排序方式

常用

将序列分成 nn\sqrt{n}*n 个长度为 n\sqrt{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 $

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

nblock\frac{n}{block} 个块,移动的总次数就是 n2block\frac{n^2}{block}

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

我们要让这个值尽量小, blockblocknm\frac{n}{\sqrt{m}}是最优的

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


转移

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


题意

abs(A[i]A[j])kabs(A[i]-A[j]) \leq k

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

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

返回llrr区间内好数数量

思路

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

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

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

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

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

  • 莫队维护左右转移

    n,mn,m同阶,分块取n\sqrt{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]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章