[学习笔记] 三维偏序

板子传送门🚪

常见转化

  • 动态二维数点 (即带修改的二维数点)

实现方法

离线做法

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;
}
posted @ 2020-10-17 15:25  BruceW  阅读(309)  评论(0编辑  收藏  举报