CDQ 分治

CDQ 分治

概述

CDQ 分治是一种分治算法。

通常的分治算法通常是形如 \(f(l,r)\) 分治到 \(f(l,mid)\)\(f(mid+1,r)\),然后再进行合并。此处的合并一般是具有可加性的。

但是对于一些问题,他的合并就是需要做一半向有一半做贡献。我们把这种分治算法就叫做 CDQ 分治。

比如说,利用归并排序求逆序对,这就是 cdq 分治的一个简单应用:

给定序列 \(a_{1,2,\dots,n},n \le 10^5\),求 \((i,j)\) 的个数使 \(i<j , a_i>a_j\)

il void solve(int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1;
	solve(l, mid);
	solve(mid + 1, r);
	int pl = l, pr = mid + 1;
	for (int i = l; i <= r; i++) {
		if ((pr > r) || (pl <= mid && a[pl] < a[pr])) {
			b[i] = a[pl++];
		} else {
			ans += mid - pl + 1;
			b[i] = a[pr++];
		}
	}
	for (int i = l; i <= r; i++) a[i] = b[i];
}

偏序问题

CDQ 分治通常用于解决多维偏序问题(一般就是三维偏序)。

二维偏序

和上述的归并排序求逆序对差不多。因为求逆序对本质上就是一个二维偏序问题。

「一本通 4.1 例 2」数星星 Stars

给定 \(x_i,y_i\),求 \((i,j)\) 的个数使 \(x_i \le x_j,y_i \le y_j\)

时间复杂度 \(O(n\log n)\)

「一本通 4.1 例 2」数星星 Stars
#include <bits/stdc++.h>
#define il inline

using namespace std;

bool Beg;
namespace Zctf1088 {
namespace IO {
	const int bufsz = 1 << 20;
	char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
	#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
	il int read() {
		int x = 0; char ch = getchar(); bool t = 0;
		while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
		while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
		return t ? -x : x;
	}
	char obuf[bufsz], *p3 = obuf, stk[50];
	#define flush() (fwrite(obuf, 1, p3 - obuf, stdout), p3 = obuf)
	#define putchar(ch) (p3 == obuf + bufsz && flush(), *p3++ = (ch))
	il void write(int x, bool t = 0) {
		int top = 0;
		x < 0 ? putchar('-'), x = -x : 0;
		do {stk[++top] = x % 10 | 48; x /= 10;} while(x);
		while (top) putchar(stk[top--]);
		t ? putchar(' ') : putchar('\n');
	}
	il void wrt() {putchar('\n');}
	struct FL {
		~FL() {flush();}
	} fl;
}
using IO::read; using IO::write; using IO::wrt;
const int N = 2e4 + 10;
int n;
struct node {
	int x, y, num;
	il bool operator < (const node & c) const {
		return x != c.x ? x < c.x : y < c.y;
	}
} a[N], b[N];
il void cdq(int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1;
	cdq(l, mid);
	cdq(mid + 1, r);
	int pl = l, pr = mid + 1;
	for (int i = l; i <= r; i++) {
		if ((pr > r) || (pl <= mid && a[pl].y <= a[pr].y)) {
			b[i] = a[pl++];
		} else {
			a[pr].num += pl - l;
			b[i] = a[pr++];
		}
	}
	for (int i = l; i <= r; i++) a[i] = b[i];
}
int t[N];
signed main() {
	n = read();
	for (int i = 1; i <= n; i++) {
		int x = read(), y = read();
		a[i] = {x, y};
	}
	sort(a + 1, a + 1 + n); 
	cdq(1, n);
	for (int i = 1; i <= n; i++) t[a[i].num]++;
	for (int i = 0; i < n; i++) write(t[i]);
	
	return 0;
}}
bool End;
il void Usd() {cerr << "\nUse: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
signed main() {
	Zctf1088::main();
	Usd();
	return 0;
}

三维偏序

那有了二维偏序的东西,就可以扩展到三维偏序。

【模板】三维偏序 / 陌上花开

给定 \(x_i,y_i,z_i\),求 \((i,j)\) 的个数使 \(x_i \le x_j,y_i \le y_j, z_i \le z_j\)

上面我们二维偏序的代码是在保证 \(x_i<x_j,y_i<y_j\) 的情况下统计贡献。现在多了第三维 \(z\),可以用数据结构进行维护即可。时间复杂度 \(O(n \log^2 n)\)

我们也可以再套一层 cdq 来做第三维。复杂度同样为 \(O(n \log ^2 n)\)

CDQ 套树状数组
#include <bits/stdc++.h>
#define il inline

using namespace std;

bool Beg;
namespace Zctf1088 {
namespace IO {
	const int bufsz = 1 << 20;
	char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
	#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
	il int read() {
		int x = 0; char ch = getchar(); bool t = 0;
		while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
		while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
		return t ? -x : x;
	}
	char obuf[bufsz], *p3 = obuf, stk[50];
	#define flush() (fwrite(obuf, 1, p3 - obuf, stdout), p3 = obuf)
	#define putchar(ch) (p3 == obuf + bufsz && flush(), *p3++ = (ch))
	il void write(int x, bool t = 0) {
		int top = 0;
		x < 0 ? putchar('-'), x = -x : 0;
		do {stk[++top] = x % 10 | 48; x /= 10;} while(x);
		while (top) putchar(stk[top--]);
		t ? putchar(' ') : putchar('\n');
	}
	il void wrt() {putchar('\n');}
	struct FL {
		~FL() {flush();}
	} fl;
}
using IO::read; using IO::write; using IO::wrt;
const int N = 2e5 + 10;
int n, kk;
struct node {
	int x, y, z, cnt, num;
	il bool operator < (const node & c) const {
		return x != c.x ? x < c.x : (y != c.y ? y < c.y : z < c.z);
	}
} a[N], b[N];
namespace BIT {
	int c[N];
	il void update(int x, int v) {
		for (int i = x; i <= kk; i += (i & -i)) {
			c[i] += v;
		}
	}
	il int query(int x) {
		int res = 0;
		for (int i = x; i; i -= (i & -i)) {
			res += c[i];
		}
		return res;
	}
}
il void cdq(int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1;
	cdq(l, mid), cdq(mid + 1, r);
	int pl = l, pr = mid + 1;
	for (int i = l; i <= r; i++) {
		if (pr > r || (pl <= mid && a[pl].y <= a[pr].y)) {
			BIT::update(a[pl].z, a[pl].cnt);
			b[i] = a[pl++];
		} else {
			a[pr].num += BIT::query(a[pr].z);
			b[i] = a[pr++];
		}
	}
	for (int i = l; i <= mid; i++) BIT::update(a[i].z, -a[i].cnt);
	for (int i = l; i <= r; i++) a[i] = b[i];
}
int t[N];
signed main() {
	n = read(), kk = read();
	for (int i = 1; i <= n; i++) {
		int x = read(), y = read(), z = read();
		a[i] = {x, y, z};
	}
	sort(a + 1, a + 1 + n);
	int m = 0;
	for (int i = 1, j = 0; i <= n; i++) {
		if (a[i].x == a[j].x && a[i].y == a[j].y && a[i].z == a[j].z) {
			a[j].cnt++;
		} else {
			a[++j] = a[i];
			a[j].cnt = 1;
			m++;
		}
	}
	cdq(1, m);
	for (int i = 1; i <= m; i++) {
		t[a[i].num + a[i].cnt - 1] += a[i].cnt;
	}
	for (int i = 0; i < n; i++) write(t[i]);
	
	return 0;
}}
bool End;
il void Usd() {cerr << "\nUse: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
signed main() {
	Zctf1088::main();
	Usd();
	return 0;
}
CDQ 套 CDQ
#include <bits/stdc++.h>
#define il inline

using namespace std;

bool Beg;
namespace Zctf1088 {
namespace IO {
	const int bufsz = 1 << 20;
	char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
	#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
	il int read() {
		int x = 0; char ch = getchar(); bool t = 0;
		while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
		while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
		return t ? -x : x;
	}
	char obuf[bufsz], *p3 = obuf, stk[50];
	#define flush() (fwrite(obuf, 1, p3 - obuf, stdout), p3 = obuf)
	#define putchar(ch) (p3 == obuf + bufsz && flush(), *p3++ = (ch))
	il void write(int x, bool t = 0) {
		int top = 0;
		x < 0 ? putchar('-'), x = -x : 0;
		do {stk[++top] = x % 10 | 48; x /= 10;} while(x);
		while (top) putchar(stk[top--]);
		t ? putchar(' ') : putchar('\n');
	}
	il void wrt() {putchar('\n');}
	struct FL {
		~FL() {flush();}
	} fl;
}
using IO::read; using IO::write; using IO::wrt;
const int N = 2e5 + 10;
int n, kk;
struct node {
	int x, y, z, cnt, num, id, fl;
	il bool operator < (const node & c) const {
		return x != c.x ? x < c.x : (y != c.y ? y < c.y : z < c.z);
	}
} a[N], a1[N], b[N];
il void cdq1(int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1;
	cdq1(l, mid), cdq1(mid + 1, r);
	int pl = l, pr = mid + 1, res = 0;
	for (int i = l; i <= r; i++) {
		if (pr > r || (pl <= mid && a1[pl].z <= a1[pr].z)) {
			res += (a1[pl].fl == 0) * a1[pl].cnt;
			b[i] = a1[pl++];
		} else {
			a[a1[pr].id].num += (a1[pr].fl == 1) * res;
			b[i] = a1[pr++];
		}
	}
	for (int i = l; i <= r; i++) a1[i] = b[i];
}
il void cdq(int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1;
	cdq(l, mid), cdq(mid + 1, r);
	int pl = l, pr = mid + 1;
	for (int i = l; i <= r; i++) {
		if (pr > r || (pl <= mid && a[pl].y <= a[pr].y)) {
			a[pl].fl = 0;
			b[i] = a[pl++];
		} else {
			a[pr].fl = 1;
			b[i] = a[pr++];
		}
	}
	for (int i = l; i <= r; i++) {
		a1[i] = a[i] = b[i];
		a1[i].id = i;
	}
	cdq1(l, r);
}
int t[N];
signed main() {
	n = read(), kk = read();
	for (int i = 1; i <= n; i++) {
		int x = read(), y = read(), z = read();
		a[i] = {x, y, z};
	}
	sort(a + 1, a + 1 + n);
	int m = 0;
	for (int i = 1, j = 0; i <= n; i++) {
		if (a[i].x == a[j].x && a[i].y == a[j].y && a[i].z == a[j].z) {
			a[j].cnt++;
		} else {
			a[++j] = a[i];
			a[j].cnt = 1;
			m++;
		}
	}
	cdq(1, m);
	for (int i = 1; i <= m; i++) {
		t[a[i].num + a[i].cnt - 1] += a[i].cnt;
	}
	for (int i = 0; i < n; i++) write(t[i]);
	
	return 0;
}}
bool End;
il void Usd() {cerr << "\nUse: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
signed main() {
	Zctf1088::main();
	Usd();
	return 0;
}

四维偏序

这玩意儿没啥用。可以加深理解。

【模板】四维偏序

给定 \(x_i,y_i,z_i,w_i\),求 \((i,j)\) 的个数使 \(x_i \le x_j,y_i \le y_j, z_i \le z_j,w_i \le w_j\)

给出一个 cdq 套 cdq 套树状数组的写法。时间复杂度 \(O(n \log^3 n)\)

四维偏序
#include <bits/stdc++.h>
#define il inline

using namespace std;

bool Beg;
namespace Zctf1088 {
namespace IO {
	const int bufsz = 1 << 20;
	char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
	#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
	il int read() {
		int x = 0; char ch = getchar(); bool t = 0;
		while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
		while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
		return t ? -x : x;
	}
	char obuf[bufsz], *p3 = obuf, stk[50];
	#define flush() (fwrite(obuf, 1, p3 - obuf, stdout), p3 = obuf)
	#define putchar(ch) (p3 == obuf + bufsz && flush(), *p3++ = (ch))
	il void write(int x, bool t = 0) {
		int top = 0;
		x < 0 ? putchar('-'), x = -x : 0;
		do {stk[++top] = x % 10 | 48; x /= 10;} while(x);
		while (top) putchar(stk[top--]);
		t ? putchar(' ') : putchar('\n');
	}
	il void wrt() {putchar('\n');}
	struct FL {
		~FL() {flush();}
	} fl;
}
using IO::read; using IO::write; using IO::wrt;
const int N = 1e5 + 10;
int n, kk;
struct node {
	int x, y, z, w, cnt, num, id, fl;
	il bool operator < (const node & c) const {
		return x != c.x ? x < c.x : (y != c.y ? y < c.y : (z != c.z ? z < c.z : w < c.w));
	}
} a[N], a1[N], b[N];
int li[N], ll;
namespace BIT {
	int c[N];
	il void update(int x, int v) {
		for (int i = x; i <= ll; i += (i & -i)) {
			c[i] += v;
		}
	}
	il int query(int x) {
		int res = 0;
		for (int i = x; i; i -= (i & -i)) {
			res += c[i];
		}
		return res;
	}
}
il void cdq1(int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1;
	cdq1(l, mid), cdq1(mid + 1, r);
	int pl = l, pr = mid + 1;
	for (int i = l; i <= r; i++) {
		if (pr > r || (pl <= mid && a1[pl].z <= a1[pr].z)) {
			if (a1[pl].fl == 0) BIT::update(a1[pl].w, a1[pl].cnt);
			b[i] = a1[pl++];
		} else {
			if (a1[pr].fl == 1) a[a1[pr].id].num += BIT::query(a1[pr].w);
			b[i] = a1[pr++];
		}
	}
	for (int i = l; i <= mid; i++) 
		if (a1[i].fl == 0) BIT::update(a1[i].w, -a1[i].cnt);
	for (int i = l; i <= r; i++) a1[i] = b[i];
}
il void cdq(int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1;
	cdq(l, mid), cdq(mid + 1, r);
	int pl = l, pr = mid + 1;
	for (int i = l; i <= r; i++) {
		if (pr > r || (pl <= mid && a[pl].y <= a[pr].y)) {
			a[pl].fl = 0;
			b[i] = a[pl++];
		} else {
			a[pr].fl = 1;
			b[i] = a[pr++];
		}
	}
	for (int i = l; i <= r; i++) {
		a1[i] = a[i] = b[i];
		a1[i].id = i;
	}
	cdq1(l, r);
}
int t[N];
signed main() {
	n = read(), kk = read();
	for (int i = 1; i <= n; i++) {
		int x = read(), y = read(), z = read(), w = read();
		a[i] = {x, y, z, w};
	}
	for (int i = 1; i <= n; i++) li[i] = a[i].x;
	sort(li + 1, li + 1 + n); ll = unique(li + 1, li + 1 + n) - li - 1;
	for (int i = 1; i <= n; i++) a[i].x = lower_bound(li + 1, li + 1 + ll, a[i].x) - li;
	for (int i = 1; i <= n; i++) li[i] = a[i].y;
	sort(li + 1, li + 1 + n); ll = unique(li + 1, li + 1 + n) - li - 1;
	for (int i = 1; i <= n; i++) a[i].y = lower_bound(li + 1, li + 1 + ll, a[i].y) - li;
	for (int i = 1; i <= n; i++) li[i] = a[i].z;
	sort(li + 1, li + 1 + n); ll = unique(li + 1, li + 1 + n) - li - 1;
	for (int i = 1; i <= n; i++) a[i].z = lower_bound(li + 1, li + 1 + ll, a[i].z) - li;
	for (int i = 1; i <= n; i++) li[i] = a[i].w;
	sort(li + 1, li + 1 + n); ll = unique(li + 1, li + 1 + n) - li - 1;
	for (int i = 1; i <= n; i++) a[i].w = lower_bound(li + 1, li + 1 + ll, a[i].w) - li;
	sort(a + 1, a + 1 + n);
	int m = 0;
	for (int i = 1, j = 0; i <= n; i++) {
		if (a[i].x == a[j].x && a[i].y == a[j].y && a[i].z == a[j].z && a[i].w == a[j].w) {
			a[j].cnt++;
		} else {
			a[++j] = a[i];
			a[j].cnt = 1;
			m++;
		}
	}
	cdq(1, m);
	for (int i = 1; i <= m; i++) {
		t[a[i].num + a[i].cnt - 1] += a[i].cnt;
	}
	for (int i = 0; i < n; i++) write(t[i]);
	
	return 0;
}}
bool End;
il void Usd() {cerr << "\nUse: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
signed main() {
	Zctf1088::main();
	Usd();
	return 0;
}

总结一下,\(k\) 维偏序解法都是相似的。除了第一次排序外,剩下的 \(k-1\) 层,直到最后一层前,都使用 cdq。需要记录当前点在左半边或右半边。所以理论上会有一个 \(2^{k-2}\) 的大常数。

但其实一般只能用得到三维偏序。

动态变静态

[BalkanOI 2007] Mokia 摩基亚

给定一个全零矩阵,每次操作分为两种:

  • 单点加。
  • 查询矩阵和。

\(n \le 2 \times 10^6\)

这个就是一个比较经典的二维的单点修改区间查询。cdq 可以做这类问题。

首先,把所有询问离线下来并将查询操作做差分,即将一个去区间询问拆成四个前缀查询。

然后,考虑横纵坐标看成第一维和第二维,把时间看成第三维,你会发现这就是一个三维偏序问题,只不过是从上面的统计数量变为求和。

Code
#include <bits/stdc++.h>
#define il inline

using namespace std;

bool Beg;
namespace Zctf1088 {
namespace IO {
	const int bufsz = 1 << 20;
	char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
	#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
	il int read() {
		int x = 0; char ch = getchar(); bool t = 0;
		while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
		while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
		return t ? -x : x;
	}
	char obuf[bufsz], *p3 = obuf, stk[50];
	#define flush() (fwrite(obuf, 1, p3 - obuf, stdout), p3 = obuf)
	#define putchar(ch) (p3 == obuf + bufsz && flush(), *p3++ = (ch))
	il void write(int x, bool t = 0) {
		int top = 0;
		x < 0 ? putchar('-'), x = -x : 0;
		do {stk[++top] = x % 10 | 48; x /= 10;} while(x);
		while (top) putchar(stk[top--]);
		t ? putchar(' ') : putchar('\n');
	}
	il void wrt() {putchar('\n');}
	struct FL {
		~FL() {flush();}
	} fl;
}
using IO::read; using IO::write; using IO::wrt;
const int N = 2e6 + 10;
int n, beg, tot, tim, qq;
struct node {
	int x, y, z, v, num, p;
	il bool operator < (const node & c) const {
		return x != c.x ? x < c.x : (y != c.y ? y < c.y : z < c.z);
	}
} a[N], b[N];
namespace BIT {
	int c[N];
	il void update(int x, int v) {
		for (int i = x; i <= tim; i += (i & -i)) c[i] += v;
	}
	il int query(int x) {
		int res = 0;
		for (int i = x; i > 0; i -= (i & -i)) res += c[i];
		return res;
	}
}
il void cdq(int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1;
	cdq(l, mid), cdq(mid + 1, r);
	int pl = l, pr = mid + 1;
	for (int i = l; i <= r; i++) {
		if (pr > r || (pl <= mid && a[pl].y <= a[pr].y)) {
			BIT::update(a[pl].z, a[pl].v);
			b[i] = a[pl++];
		} else {
			a[pr].num += BIT::query(a[pr].z);
			b[i] = a[pr++];
		}
	}
	for (int i = l; i <= mid; i++) BIT::update(a[i].z, -a[i].v);
	for (int i = l; i <= r; i++) a[i] = b[i];
}
int ans[N];
signed main() {
	beg = read(), n = read();
	while (true) {
		tim++;
		int op = read();
		if (op == 3) break;
		if (op == 1) {
			int x = read(), y = read(), v = read();
			a[++tot] = {x, y, tim, v};
		} else {
			qq++;
			int x = read(), y = read(), z = read(), w = read();
			a[++tot] = {x - 1, y - 1, tim}; a[tot].p = qq;
			a[++tot] = {x - 1, w, tim}; a[tot].p = -qq;
			a[++tot] = {z, y - 1, tim}; a[tot].p = -qq;
			a[++tot] = {z, w, tim}; a[tot].p = qq;
		}
	}
	sort(a + 1, a + 1 + tot);
	cdq(1, tot);
	for (int i = 1; i <= tot; i++) 
		ans[abs(a[i].p)] += (a[i].num + beg * a[i].x * a[i].y) * (a[i].p < 0 ? -1 : 1);
	for (int i = 1; i <= qq; i++) 
		write(ans[i]);
	return 0;
}}
bool End;
il void Usd() {cerr << "\nUse: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
signed main() {
	Zctf1088::main();
	Usd();
	return 0;
}

1D1D

cdq 还可用于优化 1D1D 的 dp 转移。只不过具体代码为适应从前到后的转移,书写方式会有所变化罢了。

写累了,放个三维 LIS 板子和代码便罢。

[HEOI2016/TJOI2016] 序列

#include <bits/stdc++.h>
#define il inline
#define int long long

using namespace std;

bool Beg;
namespace Zctf1088 {
namespace IO {
	const int bufsz = 1 << 20;
	char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
	#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
	il int read() {
		int x = 0; char ch = getchar(); bool t = 0;
		while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
		while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
		return t ? -x : x;
	}
	char obuf[bufsz], *p3 = obuf, stk[50];
	#define flush() (fwrite(obuf, 1, p3 - obuf, stdout), p3 = obuf)
	#define putchar(ch) (p3 == obuf + bufsz && flush(), *p3++ = (ch))
	il void write(int x, bool t = 0) {
		int top = 0;
		x < 0 ? putchar('-'), x = -x : 0;
		do {stk[++top] = x % 10 | 48; x /= 10;} while(x);
		while (top) putchar(stk[top--]);
		t ? putchar(' ') : putchar('\n');
	}
	il void wrt() {putchar('\n');}
	struct FL {
		~FL() {flush();}
	} fl;
}
using IO::read; using IO::write; using IO::wrt;
const int N = 1e5 + 10, maxn = 1e5;
int n, m, v[N], mn[N], mx[N];
struct node {
	int p, mn, mx, v, f;
} a[N];
il bool cmp1(node a, node b) {return a.p < b.p;}
il bool cmp2(node a, node b) {return a.mn != b.mn ? a.mn < b.mn : a.p < b.p;}
il bool cmp3(node a, node b) {return a.v != b.v ? a.v < b.v : a.p < b.p;}
namespace Seg {
	int mx[N << 2];
	#define lc p << 1
	#define rc p << 1 | 1
	#define mid ((l + r) >> 1)
	il void update(int p, int l, int r, int x, int v) {
		if (l == r) {mx[p] = max(mx[p], v); return;}
		if (x <= mid) update(lc, l, mid, x, v);
		else update(rc, mid + 1, r, x, v);
		mx[p] = max(mx[lc], mx[rc]);
	}
	il void modify(int p, int l, int r, int x) {
		mx[p] = 0;
		if (l == r) return;
		if (x <= mid) modify(lc, l, mid, x);
		else modify(rc, mid + 1, r, x);
	}
	il int query(int p, int l, int r, int x, int y) {
		if (l == x && y == r) return mx[p];
		if (y <= mid) return query(lc, l, mid, x, y);
		else if (x > mid) return query(rc, mid + 1, r, x, y);
		else return max(query(lc, l, mid, x, mid), query(rc, mid + 1, r, mid + 1, y));
	}
	#undef mid
}
il void cdq(int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1;
	cdq(l, mid);
	sort(a + l, a + mid + 1, cmp3);
	sort(a + mid + 1, a + r + 1, cmp2);
	int pl = l, pr = mid + 1;
	for (int i = l; i <= r; i++) {
		if (pr > r || (pl <= mid && a[pl].v <= a[pr].mn)) {
			Seg::update(1, 1, maxn, a[pl].mx, a[pl].f);
			pl++;
		} else {
			a[pr].f = max(a[pr].f, Seg::query(1, 1, maxn, 1, a[pr].v) + 1);
			pr++;
		}
	}
	for (int i = l; i <= mid; i++) Seg::modify(1, 1, maxn, a[i].mx);
	sort(a + mid + 1, a + r + 1, cmp1);
	cdq(mid + 1, r);
}
signed main() {
	n = read(), m = read();
	for (int i = 1; i <= n; i++) v[i] = mn[i] = mx[i] = read();
	for (int i = 1; i <= m; i++) {
		int x = read(), y = read();
		mn[x] = min(mn[x], y);
		mx[x] = max(mx[x], y);
	}
	for (int i = 1; i <= n; i++) 
		a[i] = {i, mn[i], mx[i], v[i], 1};
	cdq(1, n);
	int ans = 0;
	for (int i = 1; i <= n; i++) ans = max(ans, a[i].f);
	write(ans);
	
	return 0;
}}
bool End;
il void Usd() {cerr << "\nUse: " << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n";}
signed main() {
	Zctf1088::main();
	Usd();
	return 0;
}
posted @ 2025-12-10 20:05  Zctf1088  阅读(35)  评论(0)    收藏  举报