莫隊學習筆記和題目集

莫隊

莫隊算法一般分爲兩類

  • 莫隊維護區間答案
  • 維護區間內的數據結構
  • 樹上莫隊,帶修改莫隊、二維莫隊等等

普通莫隊

  • 將詢問離線排序處理,使轉移的次數儘量少
  • 基於分塊思想優化
    • 若在其 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]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章