[2021 CCPC 广州 G] Slope
https://codeforces.com/gym/103415/problem/G
题意:
给定二维平面和上面的点,保证所有点的横坐标不同。每次询问一个矩形范围内所有点对\((i, j)\)得到的\(\lvert \frac{y_i-y_j}{x_i-x_j}\rvert\)中的最小值。
思路:
上述式子即斜率的绝对值尽量小,其实最小的斜率一定是给定矩形中,按y值排序后,相邻的点构成的斜率。我们可以想象一下,在坐标系内有两点\(a\),\(b\)共线,假设中间有个点\(c\),它的\(y\)值在两点之间,要么三点共线,要么\(a,b\)与\(c\)的连线,一定有一个斜率会比\(ab\)大,一个斜率会比\(ab\)小。
那么如果我们将点按照\(y\)值排序后,维护每个点和它后面点的斜率,这个问题就变成了二维数点的变种。再看看题目数据和时间,并且\(x\)各不相同,估计能用莫队套线段树来做(逆天),莫队移动\(x\),线段树维护斜率,区间询问。
考虑离散化,首先不能去重,不然会少掉\(0\)的情况。标程给出的方法是对\(x\)和\(y\)分别进行离散化,这样避免了线段树和莫队中多余的点。在添加和修改的时候,用\(set\)来维护当前插入的y值,前面和后面有没有点(离散化时,相同\(y\)值的标号也要不一样),插入时将它们和当前点相连,删除时将前后点相连。
计算一下时间复杂度,设块的大小为\(T\),那么复杂度为\(O(\frac{N^2}{T}log_N + QTlog_n)\),令\(\frac{N^2}{Q} = T^2\),得到\(T = \sqrt{\frac{N^2}{Q}}\)。
细节还是挺多的,调了一天总算调明白了。具体的代码里给注释。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 7e3 + 7;
const int MAX = 1e9 + 7;
int n, q, sz;
set<int> st;
ll gcd(ll a, ll b) {
if (b == 0) return a;
return gcd(b, a % b);
}
pair<int, int> p[N], yy[N];
struct Query{
int xl, xr, yl, yr, id;
void init(int i) {
scanf("%d %d %d %d", &xl, &xr, &yl, &yr);
// 因为离散化时,相同值也会进行不同的标号,所以左端点取相同值的最小值,右端点取最大值
xl = lower_bound(p + 1, p + 1 + n, make_pair(xl, 0)) - p;
xr = lower_bound(p + 1, p + 1 + n, make_pair(xr, MAX)) - p - 1;
yl = lower_bound(yy + 1, yy + 1 + n, make_pair(yl, 0)) - yy;
yr = lower_bound(yy + 1, yy + 1 + n, make_pair(yr, MAX)) - yy - 1;
id = i;
}
bool operator < (const Query &a) const {
return xl / sz != a.xl / sz? xl < a.xl : ((xl / sz) & 1)? xr > a.xr : xr < a.xr;
}
}que[700005];
struct Frac {
int y, x;
void out() {
int g = gcd(x, y);
printf("%d %d\n", y / g, x / g);
}
friend bool operator < (const Frac &a, const Frac &b) {
return 1ll * a.y * b.x < 1ll * b.y * a.x;
}
}ans[700005];
#define mid (l + r >> 1)
#define ls (id << 1)
#define rs (id << 1 | 1)
struct Seg {
Frac t[N << 2];
void up(int id) {
t[id] = min(t[ls], t[rs]);
}
void build(int id, int l, int r) {
t[id] = {1, 0}; // 点不够输出{1, 0},我们发现直接初始化为这个值,就能优先把别的值push上去
if (l == r) {
return;
}
build(ls, l, mid);
build(rs, mid + 1, r);
}
void modify(int id, int l, int r, int p, Frac f) {
if (l == r) {
t[id] = f;
return;
}
if (p <= mid) modify(ls, l, mid, p, f);
else modify(rs, mid + 1, r, p, f);
up(id);
}
Frac query(int id, int l, int r, int ql, int qr) {
if (l > r || ql > qr) return {1, 0};
if (l >= ql && r <= qr) return t[id];
Frac f = {1, 0};
if (ql <= mid) f = min(f, query(ls, l, mid, ql, qr));
if (qr > mid) f = min(f, query(rs, mid + 1, r, ql, qr));
return f;
}
}seg;
Frac calc(int i, int j) {
return {abs(yy[j].first - yy[i].first), abs(p[yy[j].second].first - p[yy[i].second].first)};
}
void add(int x) {
x = p[x].second;
auto it = st.insert(x).first;
int l = 0, r = 0;
if (it != st.begin()) { // 判断左边是否有点
it--;
l = *it;
it++;
}
it++;
if (it != st.end()) { // 判断右边是否有点
r = *it;
}
it--;
if (l) {
seg.modify(1, 1, n, l, calc(l, x));
}
if (r) {
seg.modify(1, 1, n, x, calc(x, r));
}
}
void del(int x) {
x = p[x].second;
auto it = st.find(x);
int l = 0, r = 0;
if (it != st.begin()) {
it--;
l = *it;
it++;
}
it++;
if (it != st.end()) {
r = *it;
}
it--;
st.erase(it);
if (l) {
if (r) seg.modify(1, 1, n, l, calc(l, r));
else seg.modify(1, 1, n, l, {1, 0});
}
if (r) {
seg.modify(1, 1, n, x, {1, 0});
}
}
Frac query(int ql, int qr) {
// 这里要取放进线段树的y值中,最后一个小于等于qr的值。
// 因为每个点维护它和后面一个点的斜率,qr和边界中间可能还有值,直接取qr的话,可能会把非法的斜率放进来
auto it = st.upper_bound(qr);
if (it == st.begin()) return {1, 0};
it--;
qr = *it;
return seg.query(1, 1, n, ql, qr - 1);
}
void run() {
scanf("%d %d", &n, &q);
for (int i = 1, x, y; i <= n; ++i) {
scanf("%d %d", &x, &y);
p[i] = {x, y};
}
sort(p + 1, p + 1 + n);
sz = sqrt(1.0 * n * n / q + 1);
for (int i = 1; i <= n; ++i) {
yy[i] = {p[i].second, i};
}
sort(yy + 1, yy + 1 + n);
for (int i = 1; i <= n; ++i) {
p[yy[i].second].second = i; // yy中的second对应原p中的下标
}
for (int i = 1; i <= q; ++i) que[i].init(i);
sort(que + 1, que + 1 + q);
seg.build(1, 1, n);
int pl = 1, pr = 0;
for (int i = 1; i <= q; ++i) {
int l = que[i].xl, r = que[i].xr, id = que[i].id;
if (l > r || l > n) {
ans[id] = {1, 0};
continue;
}
while (r > pr) add(++pr);
while (l < pl) add(--pl);
while (r < pr) del(pr--);
while (l > pl) del(pl++);
ans[id] = query(que[i].yl, que[i].yr);
}
for (int i = 1; i <= q; ++i) {
ans[i].out();
}
}
int main() {
int t = 1;
while (t--) run();
return 0;
}