[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;
}
posted @ 2021-11-19 21:35  stff577  阅读(159)  评论(0编辑  收藏  举报