[Violet]天使玩偶/SJY摆棋子

知识点: CDQ分治

原题面 Luogu darkbzoj


会了不会写草,细节有点恶心。
没怎么卡常,时限 4s,不吸氧 3.95s AC 太刺激了。
darkbzoj 时限没那么紧,大概是 7s?KD-tree 的哥哥们可以一试。


题意简述

给定 \(n\) 个点的坐标,\(m\) 个操作。
每次操作给定坐标 \((x,y)\),是下列两种形式之一:

  1. \((x,y)\) 处添加一个点。
  2. 询问距离 \((x,y)\) 曼哈顿距离最小的点 的曼哈顿距离。

两个点 \(A(x_1,y_1)\)\(B(x_2,y_2)\) 的曼哈顿距离定义为:\(dist(A,B) = |x_1-x_2|+|y_1-y_2|\)
\(1\le n,m\le 3\times 10^5\)\(0\le x_i,y_i\le 10^6\)


分析题意

一个曼哈顿距离的套路:

对于一个询问 \((x,y)\),考虑将平面分为四部分,每部分的点分开考虑。

  1. \(x_i < x, y_i < y\),曼哈顿距离为 \(x - x_i + y - y_i\)
  2. \(x_i > x, y_i < y\),曼哈顿距离为 \(x_i - x + y - y_i\)
  3. \(x_i < x, y_i > y\),曼哈顿距离为 \(x - x_i + y_i - y\)
  4. \(x_i > x, y_i > y\),曼哈顿距离为 \(x_i - x + y_i - y\)

以第一种情况为例,其实际意义为位于 \((x,y)\) 左下方的点的贡献。
贡献可以写成:\((x+y)-(x_i+y_i)\)
\(x+y\) 是一个已知的常量,欲使上述贡献尽可能小,则应取最大的 \((x_i+y_i)\)

其他情况只需对坐标进行正负转换,即可转化为第一种情况。
每次转化后仅需计算第一种情况的贡献,即 \((x,y)\) 左下方的点的贡献。

应用到这种拆曼哈顿距离的套路的一个 ACM 题:2018-2019 ACM-ICPC, Asia Shenyang Regional Contest, E. The Kouga Ninja Scrolls


对于第 \(i\) 个操作,记录其时间戳为 \(t_i\)
将初始存在的点也看做添加点的操作。

对于每一个询问 \((x_i,y_i)\),仅考虑其左下方的点的贡献。
则对其有贡献的点 \((x_j,y_j)\),一定满足下列条件:
\(t_j < t_i\)\(x_j<x_i\)\(y_j<y_i\)
是一个喜闻乐见的三维偏序形式,直接上 Cdq。
\((x_j,y_j)\) 的贡献为 \((x_j+y_j)\),最优的贡献应为 \(\max{(x_j+y_j)}\)
用线段树 / 树状数组维护前缀最大值即可。


然后是喜闻乐见的卡常环节。

  1. 读入后的所有操作一定是按照时间排序的。
    考虑记录下来,在 Cdq 之前为被操作数组赋值。
    可以避免重新排序,复杂度少一个 \(\log\)
  2. 每次 Cdq 之前删除在所有询问右上方的节点。
    它们一定对答案无贡献。
  3. Cdq 中使用树状数组维护前缀最大值。
  4. Cdq 中不要写 sort!在处理跨越 \(mid\) 的询问时顺便归并即可。

最后注意一些奇怪的细节,详见代码。


代码实现

//知识点:CDQ 分治,曼哈顿距离 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <ctype.h>
#include <cstring>
#define ll long long
const int kMaxn = 1e6 + 10;
//=============================================================
struct Data {
  bool type; //0 放 1 查 
  int t, x, y;
} a[kMaxn], ori[kMaxn], tmp[kMaxn];
int n, m, maxx, maxy, ans[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void GetMin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
namespace TreeArray {
  #define lowbit(x) (x&-x)
  const int kMaxm = 1e6;
  
  int time, ti[kMaxn + 10], t[kMaxm + 10];
  void Clear() {
    time ++;
  }
  void Add(int pos_, int val_) {
    for (; pos_ <= maxy; pos_ += lowbit(pos_)) {
      if (ti[pos_] < time) {
        t[pos_] = 0;
        ti[pos_] = time;
      }
      GetMax(t[pos_], val_);
    }
  }
  int Max(int pos_) {
    int ret = 0;
    for (; pos_; pos_ -= lowbit(pos_)) {
      if (ti[pos_] < time) {
        t[pos_] = 0;
        ti[pos_] = time;
      }
      GetMax(ret, t[pos_]);
    }
    return ret;
  }
  #undef lowbit
};
#define mid ((l_+r_)>>1)
void Cdq(int l_, int r_) {
  if (l_ == r_) return;
  Cdq(l_, mid), Cdq(mid + 1, r_);
  
  int p1 = l_, p2 = mid + 1, p = l_ - 1;
  for (; p2 <= r_; ++ p2) {
    for (; p1 <= mid && a[p1].x <= a[p2].x; ++ p1) {
      tmp[++ p] = a[p1];
      if (! a[p1].type) TreeArray :: Add(a[p1].y, a[p1].x + a[p1].y);
    }
    tmp[++ p] = a[p2];
    if (! a[p2].type) continue ;
    int ret = TreeArray :: Max(a[p2].y);
    if (ret) GetMin(ans[a[p2].t], a[p2].x + a[p2].y - ret);
  }
  for (; p1 <= mid; ++ p1) tmp[++ p] = a[p1];
  for (int i = l_; i <= r_; ++ i) a[i] = tmp[i];
  TreeArray :: Clear();
}
void Delete() { //去除对询问无贡献的点
  int qmaxx = - 1, qmaxy = - 1; 
  for (int i = 1; i <= n; ++ i) {
    if (a[i].type) {
      GetMax(qmaxx, a[i].x);
      GetMax(qmaxy, a[i].y);
    }
  }
  
  m = 0;
  for (int i = 1; i <= n; ++ i) {
    if (a[i].x <= qmaxx && a[i].y <= qmaxy) {
      a[++ m] = a[i];
    }
  }
}
void Solve() {
  for (int i = 1; i <= n; ++ i) ori[i] = a[i];
  memset(ans, 63, sizeof (ans));
  maxx ++, maxy ++;
  
  Delete(); Cdq(1, m);
  
  for (int i = 1; i <= n; ++ i) {
    a[i] = ori[i];
    a[i].x = maxx - a[i].x;
  }
  Delete(); Cdq(1, m);
  
  for (int i = 1; i <= n; ++ i) {
    a[i] = ori[i];
    a[i].y = maxy - a[i].y;
  }
  Delete(); Cdq(1, m);
  
  for (int i = 1; i <= n; ++ i) {
    a[i] = ori[i];
    a[i].x = maxx - a[i].x;
    a[i].y = maxy - a[i].y;
  }
  Delete(); Cdq(1, m);
}
//=============================================================
int main() {
  n = read(), m = read();
  maxx = - 1, maxy = - 1;
  for (int i = 1; i <= n; ++ i) {
    a[i] = (Data) {false, i, read() + 1, read() + 1};
    GetMax(maxx, a[i].x), GetMax(maxy, a[i].y);
  }
  for (int i = 1; i <= m; ++ i) {
    a[++ n] = (Data) {(read() == 2), n, read() + 1, read() + 1};
    GetMax(maxx, a[n].x), GetMax(maxy, a[n].y);
  }
  
  Solve();
  for (int i = 1; i <= n; ++ i) {
    if (ori[i].type) printf("%d\n", ans[i]);
  }
  return 0;
}
posted @ 2020-08-31 22:23  Luckyblock  阅读(175)  评论(0编辑  收藏  举报