USACO 2020 January Contest, Platinum Problem 3. Falling Portals

题目链接:http://usaco.org/index.php?page=viewproblem2&cpid=998

题解:挺有意思的一道题。对于可以传送的i,j必然存在一个t>=0使得A[i]-ti=A[j]-tj,移项,发现t=(A[i]-A[j])/(i-j),也就是说t是点(i,A[i])和(j,A[j])连线的斜率。并且,如果连续传送,t必须是增大的。所以直线必须是越来越斜的。我们可以想象成开始时有一块木板绕着(i, A[i])转动表示时间的流动,当木板逆时针扫到另一个点则表明,此时木板可以转换支点,也就是奶牛传送。不妨先考虑A[Q[i]]>A[i]的情况。木板开始以i为支点逆时针转动,当木板第一个碰到的就是Q[i]时直接传送为最优解。否则,木板可以碰到i的左下方的点,那么就传送到左下方。木板继续转动,直到碰到Q[i]。如何来模拟这个过程?我们发现最优解必定在i的左下方包括i,而且最后一步,必定也是在(Q[i], A[Q[i]])的左下方。也就是说对于所有j<i,j<Q[i],A[j]<A[i]的点,我们只要找到

  min( (A[j]-A[Q[i]]) / (j-Q[i])),如果找不到当然是-1.

  不难设计出一个算法。先对A[i]排序,先做A[Q[i]]>A[i]的情况,反过来是一样的。对于i下面的点做凸包。点连起来就一定会是这样的。

显然答案也是凸的,而且是一个单峰函数,所以我们可以三分。并且我们要先在凸包中找到区间的横坐标都小于Q[i]和i的。

代码:

#include <bits/stdc++.h>

using namespace std;

const int maxn = 2e5 + 5;

int N, n, ex;
int A[maxn];

struct Cow {
    int x, y, q;
    
    Cow(int x=0, int y=0)
        : x(x), y(y) {}
    
    Cow operator - (Cow b) {
        return Cow(x-b.x, y-b.y);
    }
}cows[maxn], ch[maxn];

int ansnum[maxn], ansdet[maxn];

bool cmp(Cow A, Cow B) {
    return A.y < B.y;
}

long long Cross(Cow A, Cow B) {
    return (long long)A.x*B.y - (long long)A.y*B.x;
}

int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a%b);
}

double F(int q, int i) {
    return (double)(A[q]-ch[i].y)/(q-ch[i].x);
}

int find(int x, int L, int R) {
    if (L > R) return -1;
    while (R-L > 2) {
        int m1 = L + (R-L+1)/3;
        int m2 = R - (R-L+1)/3;
        if (F(x, m1) >= F(x, m2)) L = m1;
        else R = m2;
    }
    if (L == R) {
        if (F(x, L) <= 0) return -1;
        return L;
    }
    if (L+1 == R) {
        if (F(x, L) <= F(x, R)) return L;
        return R;
    }
    if (F(x, L) <= F(x, L+1) && F(x, L) <= F(x, R)) return L;
    else if (F(x, L+1) <= F(x, R)) return L+1;
    return R;
}

int main() {
    freopen("falling.in", "r", stdin);
    freopen("falling.out", "w", stdout);
    scanf("%d", &N);
    for (int i = 1; i <= N; ++i) {
        scanf("%d", &A[i]);
        cows[i] = Cow(i, A[i]);
        ansdet[i] = 1;
    }
    for (int i = 1; i <= N; ++i) scanf("%d", &cows[i].q);
    memset(ansnum, -1, sizeof(ansnum));
    sort(cows + 1, cows + N + 1, cmp);
    n = 0;
    ex = 0;
    for (int i = 1; i <= N; ++i) {
        while (n > 1 && Cross(cows[i]-ch[n-2], ch[n-1]-ch[n-2]) <= 0) n--;
        if (n > 0 && ex >= n) ex = n-1;
        ch[n++] = cows[i];
        if (ch[ex].x > cows[i].x) ex = n-1;
        if (A[cows[i].q] >= cows[i].y) {
            int L = 0, R = ex, l, r;
            while (L < R) {
                int mid = (L+R) / 2;
                if (ch[mid].x <= min(cows[i].x, cows[i].q)) R = mid;
                else L = mid+1;
            }
            l = L;
            L = ex; R = n-1;
            while (L < R) {
                int mid = (L+R) / 2 + 1;
                if (ch[mid].x <= min(cows[i].x, cows[i].q)) L = mid;
                else R = mid-1;
            }
            r = R;
            int j = find(cows[i].q, l, r);
            if (j == -1) continue;
            ansnum[cows[i].x] = abs(A[cows[i].q]-ch[j].y);
            ansdet[cows[i].x] = abs(cows[i].q-ch[j].x);
        }
    }
    n = 0;
    ex = 0;
    for (int i = N; i >= 1; --i) {
        while (n > 1 && Cross(cows[i]-ch[n-2], ch[n-1]-ch[n-2]) <= 0) n--;
        if (n > 0 && ex >= n) ex = n-1;
        ch[n++] = cows[i];
        if (ch[ex].x < cows[i].x) ex = n-1;
        if (A[cows[i].q] <= cows[i].y) {
            int L = 0, R = ex, l, r;
            while (L < R) {
                int mid = (L+R) / 2;
                if (ch[mid].x >= max(cows[i].x, cows[i].q)) R = mid;
                else L = mid+1;
            }
            l = L;
            L = ex; R = n-1;
            while (L < R) {
                int mid = (L+R) / 2 + 1;
                if (ch[mid].x >= max(cows[i].x, cows[i].q)) L = mid;
                else R = mid-1;
            }
            r = R;
            int j = find(cows[i].q, l, r);
            if (j == -1) continue;
            ansnum[cows[i].x] = abs(A[cows[i].q]-ch[j].y);
            ansdet[cows[i].x] = abs(cows[i].q-ch[j].x);
        }
    }
    for (int i = 1; i <= N; ++i) {
        if (ansnum[i] == -1) printf("-1
");
        else {
            int g = gcd(ansnum[i], ansdet[i]);
            printf("%d/%d
", ansnum[i]/g, ansdet[i]/g);
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/albert7xie/p/12510226.html