[Violet]天使玩偶/SJY摆棋子
知识点: CDQ分治
原题面 Luogu darkbzoj
扯
会了不会写草,细节有点恶心。
没怎么卡常,时限 4s,不吸氧 3.95s AC 太刺激了。
darkbzoj 时限没那么紧,大概是 7s?KD-tree 的哥哥们可以一试。
题意简述
给定 \(n\) 个点的坐标,\(m\) 个操作。
每次操作给定坐标 \((x,y)\),是下列两种形式之一:
- 在 \((x,y)\) 处添加一个点。
- 询问距离 \((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)\),考虑将平面分为四部分,每部分的点分开考虑。
- \(x_i < x, y_i < y\),曼哈顿距离为 \(x - x_i + y - y_i\)。
- \(x_i > x, y_i < y\),曼哈顿距离为 \(x_i - x + y - y_i\)。
- \(x_i < x, y_i > y\),曼哈顿距离为 \(x - x_i + y_i - y\)。
- \(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)}\)。
用线段树 / 树状数组维护前缀最大值即可。
然后是喜闻乐见的卡常环节。
- 读入后的所有操作一定是按照时间排序的。
考虑记录下来,在 Cdq 之前为被操作数组赋值。
可以避免重新排序,复杂度少一个 \(\log\)。 - 每次 Cdq 之前删除在所有询问右上方的节点。
它们一定对答案无贡献。 - Cdq 中使用树状数组维护前缀最大值。
- 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;
}