cdq分治
主要思想为分治,分为三个部分:
- 左区间内部。
- 左区间对右区间。
- 右区间内部。
一个保险的标准顺序是先处理左区间,再处理左区间对右区间的贡献,最后处理右区间,这样就可以保证时序性了。
注意这种写法在处理左区间对右区间贡献是要先按标号排序分出正确的左右区间,如果是先递归左右区间则不用。
一般cdq问题在分治时需要保证当前左区间和右区间某一维有序,若处理时没有 \(O(\log n)\) 修改、查询的数据结构,可以采用归并排序的方式将时间复杂度从 \(O(n \log^2 n)\) 优化到 \(O(n \log n)\) 。
处理点对相关问题
给定一个长度为 \(n\) 的序列,统计有一些特性的点对 \((i, j)\) 的数量或找到一对点 \((i, j)\) 使得一些函数的值最大。
基本流程如下:
- 找到序列中点 \(mid\) 。
- 将所有点对分为三类:
- \(i, j \in [l, mid]\) 。
- \(i, j \in [mid + 1, r]\) 。
- \(i \in [l, mid], j \in [mid + 1, r]\) 。
- 将前两类分治处理,设法处理最后一类,一般为统计左区间对右区间的贡献。
三维偏序
有 \(n\) 个元素,每个元素有 \(a_i, b_i, c_i\) 三个属性,设 \(f(i)\) 表示满足 \(a_j \leq a_i \and b_j \leq b_i \and c_j \leq c_i \leq i \not = j\) 的 \(j\) 的数量。对于所有 \(d \in [0, n)\) ,求 \(f(i) = d\) 的 \(i\) 的数量。
\(n \leq 10^5\) ,\(a_i, b_i, c_i \leq 2 \times 10^5\)
先将序列按第一维排序,这样第一维偏序就解决了。
考虑计算 \([l, mid]\) 对 \([mid + 1, r]\) 的贡献,此时只需要满足第二、三维的偏序关系,用树状数组或再用一次 cdq 分治即可。
cdq分治套树状数组:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7, V = 2e5 + 7;
struct Node {
int a, b, c, cnt, ans;
} p[N], nd[N];
int ans[N];
int n, vlim, m;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = (c == '-');
while (c < '0' || c > '9')
c = getchar(), sign |= (c == '-');
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
namespace BIT {
int c[V];
inline void update(int x, int k) {
for (; x <= vlim; x += x & -x)
c[x] += k;
}
inline int query(int x) {
int res = 0;
for (; x; x -= x & -x)
res += c[x];
return res;
}
} // namespace BIT
void cdq(int l, int r) {
if (l == r)
return;
int mid = (l + r) >> 1;
cdq(l, mid), cdq(mid + 1, r);
sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.b < b.b; });
sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.b < b.b; });
int j = l;
for (int i = mid + 1; i <= r; ++i) {
for (; j <= mid && nd[j].b <= nd[i].b; ++j)
BIT::update(nd[j].c, nd[j].cnt);
nd[i].ans += BIT::query(nd[i].c);
}
for (--j; j >= l; --j)
BIT::update(nd[j].c, -nd[j].cnt);
}
signed main() {
n = read(), vlim = read();
for (int i = 1; i <= n; ++i)
p[i].a = read(), p[i].b = read(), p[i].c = read();
sort(p + 1, p + 1 + n, [](const Node &a, const Node &b) { return a.a == b.a ? (a.b == b.b ? a.c < b.c : a.b < b.b) : a.a < b.a; });
for (int i = 1, cnt = 1; i <= n; ++i, ++cnt)
if (p[i].a != p[i + 1].a || p[i].b != p[i + 1].b || p[i].c != p[i + 1].c)
nd[++m] = p[i], nd[m].cnt = cnt, cnt = 0;
cdq(1, m);
for (int i = 1; i <= m; ++i)
ans[nd[i].ans + nd[i].cnt - 1] += nd[i].cnt;
for (int i = 0; i < n; ++i)
printf("%d\n", ans[i]);
return 0;
}
cdq分治套cdq分治:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7, V = 2e5 + 7;
struct Node {
int a, b, c, *ans, flag;
inline bool operator == (const Node &rhs) const {
return a == rhs.a && b == rhs.b && c == rhs.c;
}
inline bool operator < (const Node &rhs) const {
return a == rhs.a ? (b == rhs.b ? c < rhs.c : b < rhs.b) : a < rhs.a;
}
} a[N], b[N], c[N];
int ans[N], f[N];
int n, vlim;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = (c == '-');
while (c < '0' || c > '9')
c = getchar(), sign |= (c == '-');
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
void cdq2(int l, int r) {
if (l == r)
return;
int mid = (l + r) >> 1;
cdq2(l, mid), cdq2(mid + 1, r);
for (int i = l, j = l, k = mid + 1, res = 0; i <= r; ++i)
if ((k > r || b[j].c <= b[k].c) && j <= mid)
c[i] = b[j++], res += c[i].flag;
else {
c[i] = b[k++];
if (!c[i].flag)
*c[i].ans += res;
}
for (int i = l; i <= r; ++i)
b[i] = c[i];
}
void cdq1(int l, int r) {
if (l == r)
return;
int mid = (l + r) >> 1;
cdq1(l, mid), cdq1(mid + 1, r);
for (int i = l, j = l, k = mid + 1; i <= r; ++i)
if ((k > r || a[j].b <= a[k].b) && j <= mid)
b[i] = a[j++], b[i].flag = 1;
else
b[i] = a[k++], b[i].flag = 0;
for (int i = l; i <= r; ++i)
a[i] = b[i];
cdq2(l, r);
}
signed main() {
n = read(), vlim = read();
for (int i = 1; i <= n; ++i)
a[i].a = read(), a[i].b = read(), a[i].c = read(), a[i].ans = ans + i;
sort(a + 1, a + 1 + n);
for (int i = n - 1; i; --i)
if (a[i] == a[i + 1])
*a[i].ans = *a[i + 1].ans + 1;
cdq1(1, n);
for (int i = 1; i <= n; ++i)
++f[ans[i]];
for (int i = 0; i < n; ++i)
printf("%d\n", f[i]);
return 0;
}
给出一个 \(1 \sim n\) 的排列,按给出顺序依次删除 \(m\) 个元素,求每次删除一个元素之前整个序列的逆序对数。
\(n \leq 10^5, m \leq 5 \times 10^4\)
逆序对本质就是二维偏序,这里再引入一个时间维即可转化为三维偏序。注意处理逆序对的时候前后都要统计贡献。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7;
struct Node {
int x, k, t;
} nd[N];
ll ans[N];
int pos[N];
int n, m;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = (c == '-');
while (c < '0' || c > '9')
c = getchar(), sign |= (c == '-');
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
namespace BIT {
int c[N];
inline void update(int x, int k) {
for (; x <= n; x += x & -x)
c[x] += k;
}
inline int query(int x) {
int res = 0;
for (; x; x -= x & -x)
res += c[x];
return res;
}
} // namespace BIT
void cdq(int l, int r) {
if (l == r)
return;
int mid = (l + r) >> 1;
cdq(l, mid), cdq(mid + 1, r);
sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
int j = l;
for (int i = mid + 1; i <= r; ++i) {
for (; j <= mid && nd[j].x < nd[i].x; ++j)
BIT::update(nd[j].k, 1);
ans[nd[i].t] += BIT::query(n) - BIT::query(nd[i].k);
}
for (--j; j >= l; --j)
BIT::update(nd[j].k, -1);
sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.x > b.x; });
sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.x > b.x; });
j = l;
for (int i = mid + 1; i <= r; ++i) {
for (; j <= mid && nd[j].x > nd[i].x; ++j)
BIT::update(nd[j].k, 1);
ans[nd[i].t] += BIT::query(nd[i].k - 1);
}
for (--j; j >= l; --j)
BIT::update(nd[j].k, -1);
}
signed main() {
n = read(), m = read();
for (int i = 1; i <= n; ++i) {
int x = read();
nd[x] = (Node) {i, x, m + 1};
}
for (int i = 1; i <= m; ++i)
nd[read()].t = i;
sort(nd + 1, nd + 1 + n, [](const Node &a, const Node &b) { return a.t > b.t; });
cdq(1, n);
for (int i = m; i; --i)
ans[i] += ans[i + 1];
for (int i = 1; i <= m; ++i)
printf("%lld\n", ans[i]);
return 0;
}
四维偏序
主流做法是cdq分治套cdq分治套树状数组,时间复杂度 \(O(n \log^3 n)\) ,实现细节较多。
高维偏序
对于一般 \(k\) 维偏序问题,利用cdq嵌套求解是时间复杂度是 \(O(n \log ^{k - 1} n)\) 。
对于高维偏序,我们一般使用 bitset
解决。
对于每一维(记当前处理维度为 \(i\) ),对于除自己以外的所有元素,把满足第 \(i\) 维偏序的元素编号组成一个集合,把所有集合求交即可。
这个做法是在线的,时间复杂度 \(O(\dfrac{n^2}{\omega})\) 。
优化1D/1D动态规划
以二维最长上升子序列为例,可以列出转移方程:
\[f_i = 1 + \max_{j = 1}^{i - 1} f_j [a_j < a_i] [b_j < b_i] \]直接转移是 \(O(n^2)\) 的,考虑cdq分治优化,假设当前处理的区间为 \(l, r\) ,流程大致如下:
- 若 \(l = r\) ,说明 \(f_l\) 已求得,直接返回即可。
- 递归处理 \([l, mid]\) 。
- 处理所有 \(j \in [l, mid], i \in [mid + 1, r]\) 的转移关系。
- 递归处理 \([mid + 1, r]\) 。
注意这里必须按标准顺序处理。
有 \(n\) 个导弹,每个导弹有两个参数 \(h, v\) 。求一个最长的序列 \(a\) ,满足 \(h, v\) 不升,输出其长度。
由于可能有多种方案,需要求出对于每个导弹,其成为最长序列中的一项的概率。
\(n \leq 5 \times 10^4\)
第一问和二维LIS是类似的,第二问实际就是包含该导弹的方案数除以总方案数。一个显然的事实是包含该导弹的方案数为前后最长序列的方案数的乘积,于是跑正反两遍cdq优化DP即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 7;
struct Node {
int h, v, id;
pair<int, double> f;
};
int n;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = (c == '-');
while (c < '0' || c > '9')
c = getchar(), sign |= (c == '-');
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
namespace Pre {
Node nd[N];
namespace BIT {
pair<int, double> f[N];
inline void update(int x, pair<int, double> k) {
for (; x; x -= x & -x)
if (k.first > f[x].first)
f[x] = k;
else if (k.first == f[x].first)
f[x].second += k.second;
}
inline void remove(int x) {
for (; x; x -= x & -x)
f[x] = make_pair(0, 0);
}
inline pair<int, double> query(int x) {
pair<int, double> res = make_pair(0, 0);
for (; x <= n; x += x & -x)
if (f[x].first > res.first)
res = f[x];
else if (f[x].first == res.first)
res.second += f[x].second;
return res;
}
} // namespace BIT
void cdq(int l, int r) {
if (l == r) {
++nd[l].f.first;
return;
}
int mid = (l + r) >> 1;
sort(nd + l, nd + r + 1, [](const Node &a, const Node &b) { return a.id < b.id; });
cdq(l, mid);
sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.h > b.h; });
sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.h > b.h; });
int j = l;
for (int i = mid + 1; i <= r; ++i) {
for (; j <= mid && nd[j].h >= nd[i].h; ++j)
BIT::update(nd[j].v, nd[j].f);
pair<int, double> res = BIT::query(nd[i].v);
if (res.first > nd[i].f.first)
nd[i].f = res;
else if (res.first == nd[i].f.first)
nd[i].f.second += res.second;
}
for (--j; j >= l; --j)
BIT::remove(nd[j].v);
cdq(mid + 1, r);
}
} // namespace Pre
namespace Suf {
Node nd[N];
namespace BIT {
pair<int, double> f[N];
inline void update(int x, pair<int, double> k) {
for (; x <= n; x += x & -x)
if (k.first > f[x].first)
f[x] = k;
else if (k.first == f[x].first)
f[x].second += k.second;
}
inline void remove(int x) {
for (; x <= n; x += x & -x)
f[x] = make_pair(0, 0);
}
inline pair<int, double> query(int x) {
pair<int, double> res = make_pair(0, 0);
for (; x; x -= x & -x)
if (f[x].first > res.first)
res = f[x];
else if (f[x].first == res.first)
res.second += f[x].second;
return res;
}
} // namespace BIT
void cdq(int l, int r) {
if (l == r) {
++nd[l].f.first;
return;
}
int mid = (l + r) >> 1;
sort(nd + l, nd + r + 1, [](const Node &a, const Node &b) { return a.id < b.id; });
cdq(mid + 1, r);
sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.h < b.h; });
sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.h < b.h; });
int j = mid + 1;
for (int i = l; i <= mid; ++i) {
for (; j <= r && nd[j].h <= nd[i].h; ++j)
BIT::update(nd[j].v, nd[j].f);
pair<int, double> res = BIT::query(nd[i].v);
if (res.first > nd[i].f.first)
nd[i].f = res;
else if (res.first == nd[i].f.first)
nd[i].f.second += res.second;
}
for (--j; j >= mid + 1; --j)
BIT::remove(nd[j].v);
cdq(l, mid);
}
} // namespace Suf
signed main() {
n = read();
vector<int> vec;
for (int i = 1; i <= n; ++i) {
Pre::nd[i].h = Suf::nd[i].h = read();
vec.emplace_back(Pre::nd[i].v = Suf::nd[i].v = read());
Pre::nd[i].id = Suf::nd[i].id = i;
}
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
for (int i = 1; i <= n; ++i) {
Pre::nd[i].v = lower_bound(vec.begin(), vec.end(), Pre::nd[i].v) - vec.begin() + 1;
Pre::nd[i].f = make_pair(0, 1);
Suf::nd[i].v = lower_bound(vec.begin(), vec.end(), Suf::nd[i].v) - vec.begin() + 1;
Suf::nd[i].f = make_pair(0, 1);
}
Pre::cdq(1, n), Suf::cdq(1, n);
sort(Pre::nd + 1, Pre::nd + 1 + n, [](const Node &a, const Node &b) { return a.id < b.id; });
sort(Suf::nd + 1, Suf::nd + 1 + n, [](const Node &a, const Node &b) { return a.id < b.id; });
pair<int, double> ans = make_pair(0, 1);
for (int i = 1; i <= n; ++i)
if (Pre::nd[i].f.first > ans.first)
ans = Pre::nd[i].f;
else if (Pre::nd[i].f.first == ans.first)
ans.second += Pre::nd[i].f.second;
printf("%d\n", ans.first);
for (int i = 1; i <= n; ++i)
if (Pre::nd[i].f.first + Suf::nd[i].f.first - 1 == ans.first)
printf("%.5lf ", Pre::nd[i].f.second * Suf::nd[i].f.second / ans.second);
else
printf("0.00000 ");
return 0;
}
将动态问题转化为静态问题
一类问题形如:
- 带修改与查询。
- 可以离线。
- 每一个修改均与之后的询问操作相关。
那么可以将所有操作会按照时间cdq分治。假设现在处理的时间区间为 \([l, r]\) ,先递归处理 \([l, mid]\) 与 \([mid + 1, r]\) 的修改-询问关系,再处理修改 \(\in [l, mid]\) 、询问 \(\in [mid + 1, r]\) 的修改-询问关系,统计这部分修改对询问的贡献。
如果修改之间相互独立,则三部分顺序无所谓,否则必须按标准顺序处理。
P4390 [BalkanOI2007] Mokia 摩基亚
给出一个 \(W \times W\) 的网格,\(n\) 次操作,每次操作为下面两种操作中的一种:
- 给某个格子加上 \(x\) 。
- 询问一个矩形中的所有数的和。
\(n \leq 10^5, W \leq 10^6\)
比较板,按上述思路分治即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 7;
struct Node {
int x, y, k, t, id;
} nd[N];
int ans[N];
int W, n, cntu, cntq;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = c == '-';
while (c < '0' || c > '9')
c = getchar(), sign |= c == '-';
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
namespace BIT {
int c[N];
inline void update(int x, int k) {
for (; x <= W; x += x & -x)
c[x] += k;
}
inline int query(int x) {
int res = 0;
for (; x; x -= x & -x)
res += c[x];
return res;
}
} // namespace BIT
void cdq(int l, int r) {
if (l == r)
return;
int mid = (l + r) >> 1;
cdq(l, mid), cdq(mid + 1, r);
sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
int j = l;
for (int i = mid + 1; i <= r; ++i) {
for (; j <= mid && nd[j].x <= nd[i].x; ++j)
if (!nd[j].id)
BIT::update(nd[j].y, nd[j].k);
if (nd[i].id > 0)
ans[nd[i].id] += BIT::query(nd[i].y);
else if (nd[i].id < 0)
ans[-nd[i].id] -= BIT::query(nd[i].y);
}
for (--j; j >= l; --j)
if (!nd[j].id)
BIT::update(nd[j].y, -nd[j].k);
}
signed main() {
int op = read();
W = read() + 1;
while ((op = read()) != 3) {
if (op == 1) {
int x = read() + 1, y = read() + 1, k = read();
++cntu, nd[++n] = (Node) {x, y, k, cntu, 0};
} else {
int x = read() + 1, y = read() + 1, xx = read() + 1, yy = read() + 1;
++cntq;
nd[++n] = (Node) {xx, yy, 0, cntu, cntq};
nd[++n] = (Node) {x - 1, yy, 0, cntu, -cntq};
nd[++n] = (Node) {xx, y - 1, 0, cntu, -cntq};
nd[++n] = (Node) {x - 1, y - 1, 0, cntu, cntq};
}
}
cdq(1, n);
for (int i = 1; i <= cntq; ++i)
printf("%d\n", ans[i]);
return 0;
}
在平面直角坐标系上维护 \(n\) 次操作,每次操作为下面两种操作中的一种:
- 加入一个点 \((x, y)\) 。
- 询问与 \((x, y)\) 曼哈顿距离最小的点。
\(n \leq 3 \times 10^5\)
分情况把绝对值拆开就行了,如对于一个 \(j \leq i, x_j \leq x_i, y_j \leq y_i\) 的情况,曼哈顿距离即为 \((x_i + y_i) - (x_j + y_j)\) ,不难发现限制条件为三维偏序,于是cdq分治处理即可。剩下情况也是类似的。
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 3e5 + 7, V = 2e6 + 7;
struct Node {
int x, y, id;
} nd[4][N << 1];
int ans[N];
int n, m, tot, cntq;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = (c == '-');
while (c < '0' || c > '9')
c = getchar(), sign |= (c == '-');
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
namespace BIT {
int c[V];
inline void prework() {
fill(c, c + V, -inf);
}
inline void update(int x, int k) {
for (; x < V; x += x & -x)
c[x] = max(c[x], k);
}
inline void remove(int x) {
for (; x < V; x += x & -x)
c[x] = -inf;
}
inline int query(int x) {
int res = -inf;
for (; x; x -= x & -x)
res = max(res, c[x]);
return res;
}
} // namespace BIT
void solve(int l, int r, Node *nd) {
if (l == r)
return;
int mid = (l + r) >> 1;
solve(l, mid, nd), solve(mid + 1, r, nd);
sort(nd + l, nd + mid + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
sort(nd + mid + 1, nd + r + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
int j = l;
for (int i = mid + 1; i <= r; ++i) {
for (; j <= mid && nd[j].x <= nd[i].x; ++j)
if (!nd[j].id)
BIT::update(nd[j].y, nd[j].x + nd[j].y);
if (nd[i].id)
ans[nd[i].id] = min(ans[nd[i].id], nd[i].x + nd[i].y - BIT::query(nd[i].y));
}
for (--j; j >= l; --j)
BIT::remove(nd[j].y);
}
signed main() {
n = read(), m = read();
for (int i = 1; i <= n; ++i) {
int x = read(), y = read() + 1;
nd[0][i] = (Node) {x, y, 0};
nd[1][i] = (Node) {V - x, y, 0};
nd[2][i] = (Node) {x, V - y, 0};
nd[3][i] = (Node) {V - x, V - y, 0};
}
for (int i = n + 1; i <= n + m; ++i) {
int op = read(), x = read(), y = read() + 1;
if (op == 1) {
nd[0][i] = (Node) {x, y, 0};
nd[1][i] = (Node) {V - x, y, 0};
nd[2][i] = (Node) {x, V - y, 0};
nd[3][i] = (Node) {V - x, V - y, 0};
} else {
++cntq;
nd[0][i] = (Node) {x, y, cntq};
nd[1][i] = (Node) {V - x, y, cntq};
nd[2][i] = (Node) {x, V - y, cntq};
nd[3][i] = (Node) {V - x, V - y, cntq};
}
}
memset(ans + 1, inf, sizeof(int) * cntq);
BIT::prework();
for (int i = 0; i < 4; ++i)
solve(1, n + m, nd[i]);
for (int i = 1; i <= cntq; ++i)
printf("%d\n", ans[i]);
return 0;
}
标签:Node,const,int,分治,nd,mid,cdq
From: https://www.cnblogs.com/wshcl/p/18338717/cdq