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; }