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 分治通常用于解决多维偏序问题(一般就是三维偏序)。
二维偏序
和上述的归并排序求逆序对差不多。因为求逆序对本质上就是一个二维偏序问题。
给定 \(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}\) 的大常数。
但其实一般只能用得到三维偏序。
动态变静态
给定一个全零矩阵,每次操作分为两种:
- 单点加。
- 查询矩阵和。
\(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 板子和代码便罢。
#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;
}

浙公网安备 33010602011771号