[学习笔记] 三维偏序
常见转化
- 动态二维数点 (即带修改的二维数点)
实现方法
离线做法
CDQ 分治
先按第一维排序,然后在分治过程中,就能保证左儿子的第一维严格小于右儿子的第一维(若有第一维相等的情况,则需在排序时按照第二维排序,以此类推),那么左儿子对右儿子的贡献就是一个二维偏序问题,维护两个指针,用树状数组做即可。
#include <algorithm>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
const int _ = 2e5 + 7;
int n, m, num[_], ans[_];
struct NODE { int ty, a, b, c, id; } p[_];
namespace CDQ {
#define mid ((l + r) >> 1)
#define lb(i) ((i) & -(i))
int c[_];
void add(int x, int w) { for (int i = x; i <= m; i += lb(i)) c[i] += w; }
int sum(int x) { int res = 0; for (int i = x; i; i -= lb(i)) res += c[i]; return res; }
bool cmp(NODE x, NODE y) { return x.b < y.b; }
void calc(int l, int r) {
if (l == r) return;
calc(l, mid);
calc(mid + 1, r);
int p1 = l, p2 = mid + 1;
while (p1 <= mid and p2 <= r) {
while (p1 <= mid and p[p1].b <= p[p2].b) {
if (p[p1].ty) add(p[p1].c, 1);
++p1;
}
while (p2 <= r and p[p2].b < p[p1].b) {
if (!p[p2].ty) num[p[p2].id] += sum(p[p2].c);
++p2;
}
}
for (int i = p2; i <= r; ++i)
if (!p[i].ty) num[p[i].id] += sum(p[i].c);
for (int i = l; i < p1; ++i)
if (p[i].ty) add(p[i].c, -1);
sort(p + l, p + r + 1, cmp);
}
#undef mid
#undef lb
}
bool cmp(NODE x, NODE y) { return x.a == y.a ? (x.b == y.b ? (x.c == y.c ? x.ty > y.ty : x.c < y.c) : x.b < y.b) : x.a < y.a; }
void Init() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
scanf("%d%d%d", &p[i].a, &p[i].b, &p[i].c);
p[n + i] = p[i];
p[i].ty = 1, p[n + i].ty = 0, p[n + i].id = i;
}
sort(p + 1, p + n + n + 1, cmp);
}
void Run() {
CDQ::calc(1, n + n);
for (int i = 1; i <= n; ++i) ++ans[num[i] - 1];
for (int i = 0; i < n; ++i) printf("%d\n", ans[i]);
}
int main() {
Init();
Run();
return 0;
}
离线树套树
新 get 到的一个很好写的做法。
本质上就是树状数组套树状数组。
这里的 “离线” 就在于:把元素插入第一层树状数组时,不立刻在第二层树状数组/线段树上修改,而是把元素存下来,最后再进行统计。
这样的话我们就不用对每个节点都开一个树状数组,因为最后统计的时候可以一个树状数组反复用,就不会炸空间了。
#include <algorithm>
#include <iostream>
#include <vector>
#define pb push_back
using namespace std;
const int _ = 2e5 + 7;
int n, m, num[_], ans[_];
struct NODE { int ty, a, b, c, id; } p[_];
namespace BIT {
#define mid ((l + r) >> 1)
#define lb(i) ((i) & -(i))
int c[_];
vector<NODE> box[_];
void add(int x, int w) { for (int i = x; i <= m; i += lb(i)) c[i] += w; }
int sum(int x) { int res = 0; for (int i = x; i; i -= lb(i)) res += c[i]; return res; }
void ins(NODE x) {
if (x.ty) for (int i = x.b; i <= m; i += lb(i)) box[i].pb(x);
else for (int i = x.b; i; i -= lb(i)) box[i].pb(x);
}
void calc() {
for (int i = 1; i <= m; ++i) {
for (auto x: box[i])
if (x.ty) add(x.c, 1);
else num[x.id] += sum(x.c);
for (auto x: box[i])
if (x.ty) add(x.c, -1);
}
}
#undef lb
}
bool cmp(NODE x, NODE y) { return x.a == y.a ? (x.b == y.b ? (x.c == y.c ? x.ty > y.ty : x.c < y.c) : x.b < y.b) : x.a < y.a; }
void Init() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
scanf("%d%d%d", &p[i].a, &p[i].b, &p[i].c);
p[n + i] = p[i];
p[i].ty = 1, p[n + i].ty = 0, p[n + i].id = i;
}
sort(p + 1, p + n + n + 1, cmp);
}
void Run() {
for (int i = 1; i <= n + n; ++i) BIT::ins(p[i]);
BIT::calc();
for (int i = 1; i <= n; ++i) ++ans[num[i] - 1];
for (int i = 0; i < n; ++i) printf("%d\n", ans[i]);
}
int main() {
Init();
Run();
return 0;
}
在线做法
P.S. 这里的 “在线” 指的是 “在线动态二维数点”,而不是 “在线三维偏序” (which 等价于四维偏序)。
树状数组套线段树
按第二维插入树状数组,按第三维在线段树上进行修改查询。
为什么第二层数据结构不用树状数组?
因为树状数组没法动态开点,而如果把全部点都开出来的话空间就炸了。
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <vector>
#define pb push_back
using namespace std;
const int _ = 2e5 + 7;
const int __ = 8e6 + 7;
int n, m, num[_], ans[_];
struct NODE { int ty, a, b, c, id; } p[_];
namespace SEG {
#define mid ((l + r) >> 1)
#define lb(i) ((i) & -(i))
int c[_], rt[_], num[__], ls[__], rs[__], tot;
void modify(int &k, int l, int r, int x, int w) {
if (!k) k = ++tot;
num[k] += w;
if (l == r) return;
if (x <= mid) modify(ls[k], l, mid, x, w);
else modify(rs[k], mid + 1, r, x, w);
}
int query(int k, int l, int r, int x) {
if (!k) return 0;
if (l == r) return num[k];
if (x <= mid) return query(ls[k], l, mid, x);
else return num[ls[k]] + query(rs[k], mid + 1, r, x);
}
void ins(NODE x) { for (int i = x.b; i <= m; i += lb(i)) modify(rt[i], 1, m, x.c, 1); }
int sum(NODE x) { int res = 0; for (int i = x.b; i; i -= lb(i)) res += query(rt[i], 1, m, x.c); return res; }
#undef lb
#undef mid
}
bool cmp(NODE x, NODE y) { return x.a == y.a ? (x.b == y.b ? (x.c == y.c ? x.ty > y.ty : x.c < y.c) : x.b < y.b) : x.a < y.a; }
void Init() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
scanf("%d%d%d", &p[i].a, &p[i].b, &p[i].c);
p[n + i] = p[i];
p[i].ty = 1, p[n + i].ty = 0, p[n + i].id = i;
}
sort(p + 1, p + n + n + 1, cmp);
}
void Run() {
for (int i = 1; i <= n + n; ++i)
if (p[i].ty) SEG::ins(p[i]);
else num[p[i].id] += SEG::sum(p[i]);
for (int i = 1; i <= n; ++i) ++ans[num[i] - 1];
for (int i = 0; i < n; ++i) printf("%d\n", ans[i]);
}
int main() {
Init();
Run();
return 0;
}