【题解】「JOISC 2021 Day2」道路建设

提供一个二分 + set 的做法。

首先二分一个距离 m i d mid mid ,考虑距离 ≤ m i d \leq mid mid 的点对个数,此时合法点对的个数 ≤ K \leq K K 。得到答案后,再将所有距离 ≤ m i d − 1 \leq mid-1 mid1 的方案都构造出来,最后输出若干个 m i d mid mid 占位。

我们来考虑直接做这道题,类似于天使玩偶,可以分两个点的位置进行讨论,内部用树状数组求答案,是一个三维偏序的经典问题 (天使玩偶),时间复杂度 O ( n l o g 3 n ) O(nlog^3n) O(nlog3n)

这里有一个有用的剪枝,就是当找到 K K K 个点对后,可以直接返回 f a l s e false false ,但是 c d q cdq cdq 的复杂度仍然是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

下面提供一个 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n) 的做法,即不会找超过 K K K 个点对,内部用一个带 l o g log log s e t set set 维护。

事实上这道题 可以转化为二维偏序。这是一个非常重要的思想,因为求没有重复元素的三维点对时也可以用二维偏序来计算。

考虑定义 a i = x i + y i a_i=x_i+y_i ai=xi+yi b i = x i − y i b_i=x_i-y_i bi=xiyi,点对的新距离为 m a x ( a b s ( a i − a j ) , a b s ( b i − b j ) ) max(abs(a_i-a_j),abs(b_i-b_j)) max(abs(aiaj),abs(bibj)) 。这其实是一个等价转换。然而不同的是,我们去掉了 x i < x j x_i<x_j xi<xj 的限制。

那么我们只需要按照 a i a_i ai 从小到大排序,后面的减去前面的就可以消去绝对值,而后面那个可以直接在 ( b i − K , b i + K ) (b_i-K,b_i+K) (biK,bi+K) 的范围内查找,即在 s e t set set 里进行二分查找。

这个方法时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n) ,巧妙运用了绝对值和 m a x max max 函数的性质。

#include <bits/stdc++.h>
#define pii pair<int,int>
#define mp(x,y) make_pair(x,y)
#define pb push_back
#define fi first
#define se second
#define int long long
using namespace std;
const int mx = 1e6 + 5;
inline int read() {
    int X = 0;
    bool flag = 1;
    char ch = getchar();

    while (ch < '0' || ch > '9') {
        if (ch == '-')
            flag = 0;

        ch = getchar();
    }

    while (ch >= '0' && ch <= '9') {
        X = (X << 1) + (X << 3) + ch - '0';
        ch = getchar();
    }

    if (flag)
        return X;

    return ~(X - 1);
}
//二分+三维偏序+set
//时间复杂度 O(nlog^3n)
//考虑当合法状态较少时,cdq的复杂度很大
//考虑扔掉一定不产生贡献的答案(实际不可行)
//考虑二维偏序的做法,启发极大
//时间复杂度 O(nlog^2n+Klog^2n) ,其中 n,K 复杂度是分开算的
int n, K, M, cnt, ans[mx];
pii q[mx], X[mx], Y[mx];
set<pii> s;
set<pii>::iterator it;
int dist(int x, int y) {
    return max(abs(q[x].fi - q[y].fi), abs(q[x].se - q[y].se));
}
bool check() {
    cnt = 0;
    s.clear();
    int j = 1;

    for (int i = 1; i <= n; i++) {
        while (j < i && q[i].fi - q[j].fi > M) {
            s.erase(mp(q[j].se, j));
            j++;
        }

        it = s.lower_bound(mp(q[i].se - M, 0));

        for (; it != s.end(); it++) {
            pii now = (*it);

            if (now.fi - q[i].se > M) {
                break;
            }

            ans[++cnt] = dist(i, now.se);

            if (cnt >= K)
                return 0;
        }

        s.insert(mp(q[i].se, i));
    }

    return cnt < K;
}
signed main() {
    //      freopen("data.in","r",stdin);
    //        freopen("b.in","r",stdin);
    //         freopen("ans.out","w",stdout);
    n = read(), K = read();

    for (int i = 1; i <= n; i++) {
        int x = read(), y = read();
        q[i] = mp(x - y, x + y);
    }

    sort(q + 1, q + 1 + n);
    //  M=3; check();
    //      M=1; if(check()) printf("AC");
    //      printf("%lld\n",cnt);
    int l = 0, r = 4e9, res = 0;

    while (l <= r) {
        M = (l + r) >> 1;

        if (check())
            l = M + 1, res = M;
        else
            r = M - 1;
    }

    //
    //    //       printf("%lld\n",res);
    M = res, check();

    sort(ans + 1, ans + 1 + cnt);

    for (int i = 1; i <= cnt; i++) {
        printf("%lld\n", ans[i]);
    }

    for (int i = 1; i <= K - cnt; i++) {
        printf("%lld\n", M + 1);
    }

    //  printf("%lld",res);
    //    for(int i=3;i<=7;i++) {
    //      M=i;
    //      if(check()) printf("%d\n",i);
    //  }
}
posted @ 2021-06-25 21:56  仰望星空的蚂蚁  阅读(28)  评论(0)    收藏  举报  来源