CDQ分治笔记

同样是两三年前就听说的知识点,现在再来学..

解决一类点对问题

不管是OI-wiki还是你谷题解第一眼看上去都有点懵,感觉是那种会的看得懂,不会的看不懂的讲解..

所以结合着题目来理解,再概括会比较好

例题 P3810 [模板]三维偏序

像逆序对就是一个经典的二维偏序问题,固定了下标之后,只剩下一维,用权值线段树、树状数组维护一下就好

那么现在固定了一维 $(i<j)$
之后,将剩下的部分改成二维的 $(a,b)$,然后求一个满足条件的偏序数量

$$
f(i)=\sum\limits_{j\ne i}{[a_j<a_i&b_j<b_i]}
$$

求 $\forall d\in [0,n-1]$,输出
$f(i)=d$ 的数量,共 $n$ 行

如果消除第一维的影响,这题就变成了一个二维偏序,用树状数组就能很容易的做掉

考虑将 $(l,r)$ 范围内的第一维分治,分成
$(l,mid)$ 和 $(mid+1,r)$ 两部分,这样左边的第一维就严格小于右边的,然后对于每一个
$j\in[mid+1,r]$,把所有 $a_i<a_j,i\in[l,mid]$ 插入到树状数组里,统计
$<b_j$ 的个数, $i,j$ 的移动只要排个序就能解决

现在是,实现时间!

放到这题里面,因为相等的元素也要算,直接处理起来比较坑爹,一个好方法是记录一个附加位置,对于相同的元素,他们的附加位置相同,附加位置作为新的id,然后强行变成严格偏序,这样的计数效果就一样了

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#include <bits/stdc++.h>

using namespace std;

#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif

#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, a, n) for (int i = n; i >= a; i--)
#define max(a, b) (a > b ? a : b)
#define min(a, b) (a < b ? a : b)
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define pi acos(-1)
#define il inline
#define rg register
#define SZ(x) ((int)(x).size())
#define all(x) x.begin(), x.end()
#define INF 0x7fffffff
#define inf 0x3f3f3f3f
#define MOD 998244353
#define mod 1000000007
typedef vector<int> VI;
typedef pair<int, int> PII;
typedef long long ll;
typedef double db;
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }

namespace FAST_IO {
#define ll long long
#define ull unsigned long long
#define db double
#define _8 __int128_t
const int LEN = 1 << 20;
char BUF[LEN], PUF[LEN];
int Pin = LEN, Pout;
inline void flushin() {
memcpy(BUF, BUF + Pin, LEN - Pin), fread(BUF + LEN - Pin, 1, Pin, stdin),
Pin = 0;
return;
}
inline void flushout() {
fwrite(PUF, 1, Pout, stdout), Pout = 0;
return;
}
inline char Getc() {
return (Pin == LEN ? (fread(BUF, 1, LEN, stdin), Pin = 0) : 0), BUF[Pin++];
}
inline char Get() { return BUF[Pin++]; }
inline void Putc(char x) {
if (Pout == LEN)
flushout(), Pout = 0;
PUF[Pout++] = x;
}
inline void Put(char x) { PUF[Pout++] = x; }
template <typename tp = int> inline tp read() {
(Pin + 32 >= LEN) ? flushin() : void();
tp res = 0;
char f = 1, ch = ' ';
for (; ch < '0' || ch > '9'; ch = Get())
if (ch == '-')
f = -1;
for (; ch >= '0' && ch <= '9'; ch = Get())
res = (res << 3) + (res << 1) + ch - 48;
return res * f;
}
template <typename tp> inline void wt(tp a) {
if (a > 9)
wt(a / 10);
Put(a % 10 + '0');
return;
}
template <typename tp> inline void write(tp a, char b = '\n') {
static int stk[20], top;
(Pout + 32 >= LEN) ? flushout() : void();
if (a < 0)
Put('-'), a = -a;
else if (a == 0)
Put('0');
for (top = 0; a; a /= 10)
stk[++top] = a % 10;
for (; top; --top)
Put(stk[top] ^ 48);
Put(b);
return;
}
inline void wt_str(std::string s) {
for (char i : s)
Putc(i);
return;
}
} // namespace FAST_IO
using namespace FAST_IO;

struct Element {
int a, b, c;
int id;
};

int main() {
int n, k;
n = read(), k = read();

vector<Element> e(n + 1);

rep(i, 1, n) e[i].a = read(), e[i].b = read(), e[i].c = read(), e[i].id = i;

sort(e.begin() + 1, e.begin() + 1 + n,
[&](const Element &x, const Element &y) {
return (x.a == y.a) ? ((x.b == y.b) ? (x.c < y.c) : (x.b < y.b))
: (x.a < y.a);
});

VI ID(n + 1), num(n + 1, 0);

for (int i = 1; i <= n;) {
int j = i + 1;
while (j <= n && e[i].a == e[j].a && e[i].b == e[j].b && e[i].c == e[j].c)
++j;
while (i < j)
ID[e[i].id] = e[j - 1].id, ++i;
}

rep(i, 1, n) e[i].a = i;

VI c(k + 1, 0);

auto mark = [&](int x, int v) {
for (; x <= k; x += x & -x)
c[x] += v;
};

auto query = [&](int x) {
int res = 0;
for (; x; x -= x & -x)
res += c[x];
return res;
};

function<void(int, int)> cdq = [&](int l, int r) {
if (l == r)
return;
int mid = (l + r) / 2;
cdq(l, mid);
cdq(mid + 1, r);
sort(e.begin() + l, e.begin() + r + 1,
[&](const Element &x, const Element &y) {
return (x.b == y.b) ? ((x.c == y.c) ? (x.a < y.a) : (x.c < y.c))
: (x.b < y.b);
});
rep(i, l, r) {
if (e[i].a <= mid)
mark(e[i].c, 1);
else
num[e[i].id] += query(e[i].c);
}

rep (i, l, r)
if (e[i].a <= mid)
mark(e[i].c, -1);
};

cdq(1, n);

VI f(n + 1, 0);
rep (i, 1, n)
f[num[ID[e[i].id]]]++;

for (int i = 0; i < n; ++i)
write(f[i], '\n');

flushout();
return 0;
}

还有一个理解,就是cdq可以嵌套,依次处理每一维,这种思想还是很屌的,可以参考洛谷题解