USACO Monthly March, 2008, Gold, Land Acquisition(土地购买)
Posted in USACO on 五月 19th, 2010 by 纳米 – Be the first to comment解题报告:
首先,对所有的(x, y)(长、宽)排序。
然后,去除所有(x, y),当且仅当存在(x’, y’),使得x’>=x && y’>=y。这一步在排序后可以在线性时间内完成。
设f(x)为购买1-x块土地所需最少的钱。x[i]、y[i]分别为第i块土地的长、宽。
有方程:f(x) = min{f(i) + w[i+1, x] | 0 <= i < x},其中,w[i, j] = x[i] * y[j]。
首先证明,w[i, j] + w[i+1, j+1] <= w[i+1, j] + w[i, j+1]:
(1) w[i, j+1] – w[i, j]
=x[i] * y[j+1] – x[i] * y[j]
=x[i] * (y[j+1]-y[j])
因为x[i]关于i单调递增,y[j+1]-y[j] < 0,所以上述结果关于i单调递减。
(2) w[i, j] – w[i+1,j]
=x[i]*y[j] – x[i+1]*y[j]
=y[j] * (x[i] – x[i+1])
因为y[j]关于j单调递减,x[i] – x[i+1] < 0,所以上述结果关于j单调递增。
综上,w[i, j] + w[i+1, j+1] <= w[i+1, j] + w[i, j+1]。
解法一时间复杂度为O(n log2 n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | #include <stdio.h> #include <stdlib.h> const char IN_FILE_NAME[] = "acquire.in"; const char OUT_FILE_NAME[] = "acquire.out"; const int maxn = 50005; int n, data[maxn][2], stack[maxn][3], last; long long dp[maxn], answer; int input() { FILE *fin = fopen(IN_FILE_NAME, "r"); fscanf(fin, "%d", &n); for (int i = 1; i <= n; ++i) fscanf(fin, "%d%d", &data[i][0], &data[i][1]); fclose(fin); return 0; } int comp(int x[], int y[]) { if (x[0]==y[0] && x[1]==y[1]) return 0; else if (x[0]>y[0] || (x[0]==y[0] && x[1]>y[1])) return -1; else return 1; } int work_init() { qsort(data[1], n, sizeof(int) << 1, (int(*) (const void*, const void*)) comp); int n0 = 0, maxy = 0; for (int i = 1; i <= n; ++i) if (data[i][1] > maxy) { maxy = data[i][1]; ++n0; data[n0][0] = data[i][0]; data[n0][1] = data[i][1]; } n = n0; return 0; } long long calc_dp(int i, int j) { return dp[j] + (long long)data[j+1][0] * (long long)data[i][1]; } int work_find(int t0, int t1, int w0, int w1) { int left = w0, right = w1, res = w1 + 1; while (left <= right) { int mid = (left + right) >> 1; if (mid > t0 && calc_dp(mid, t0) <= calc_dp(mid, t1)) { res = mid; right = mid - 1; } else { left = mid + 1; } } return res; } int work_dp() { dp[0] = last = 0; stack[0][0] = 0; stack[0][1] = 1; stack[0][2] = n; int pw = 0; for (int i = 1; i <= n; ++i) { if (pw > last) pw = last; while (i > stack[pw][2]) ++pw; dp[i] = calc_dp(i, stack[pw][0]); while (stack[last][1] > i && calc_dp(stack[last][1], i) <= calc_dp(stack[last][1], stack[last][0])) --last; int w = work_find(i, stack[last][0], stack[last][1], stack[last][2]); if (w <= n) { stack[last][2] = w - 1; ++last; stack[last][0] = i; stack[last][1] = w; stack[last][2] = n; } } answer = dp[n]; return 0; } int output() { FILE *fout = fopen(OUT_FILE_NAME, "w"); fprintf(fout, "%lld\n", answer); fclose(fout); return 0; } int main() { input(); work_init(); work_dp(); output(); return 0; } |
解法二:
f(n) = min{f(i) + W[i+1] * L[n] | 0 <= i < n},W、L分别为第一、二关键字。
设x(i) = f(i), b(i) = L[n], y(i) = W[i+1],则:
f(n) = min{x(i) + b(i) * y(i) | 0 <= i < n} x(i)是单调递增的,-1/b(i)也是单调递增的(考虑P=x + by => y = (-1/b)x + P/b,最小化截距P/b)。显然,决策点都在凸包上,所以,可以维护一个栈维护凸包,然后用一个指针进行决策。时间复杂度为O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | #include <stdio.h> #include <stdlib.h> const char IN_FILE_NAME[] = "acquire.in"; const char OUT_FILE_NAME[] = "acquire.out"; const int maxn = 50005; int n, data[maxn][2], stack[maxn], last; long long dp[maxn], stackp[maxn][2], answer; int input() { FILE *fin = fopen(IN_FILE_NAME, "r"); fscanf(fin, "%d", &n); for (int i = 1; i <= n; ++i) fscanf(fin, "%d%d", &data[i][0], &data[i][1]); fclose(fin); return 0; } int comp(int x[], int y[]) { if (x[0]==y[0] && x[1]==y[1]) return 0; else if (x[0]>y[0] || (x[0]==y[0] && x[1]>y[1])) return -1; else return 1; } int work_init() { qsort(data[1], n, sizeof(int) << 1, (int(*) (const void*, const void*)) comp); int n0 = 1; for (int i = 2; i <= n; ++i) if (data[i][1] > data[n0][1]) { ++n0; data[n0][0] = data[i][0]; data[n0][1] = data[i][1]; } n = n0; return 0; } long long calc(int i, int j) { return dp[j] + (long long)data[j+1][0] * (long long)data[i][1]; } void stack_insert(int t, long long x, long long y) { stack[++last] = t; stackp[last][0] = x; stackp[last][1] = y; } bool insert_ok(long long x2, long long y2) { long long x0 = stackp[last-1][0], y0 = stackp[last-1][1]; long long x1 = stackp[last][0], y1 = stackp[last][1]; return (x1-x0)*(y2-y0)-(x2-x0)*(y1-y0) > 0; } int work_dp() { dp[0] = 0; dp[1] = calc(1, 0); last = -1; stack_insert(0, dp[0], data[1][0]); stack_insert(1, dp[1], data[2][0]); int pw = 0; for (int i = 2; i <= n; ++i) { pw = pw < last ? pw : last; while (pw < last && calc(i, stack[pw+1]) <= calc(i, stack[pw])) ++pw; dp[i] = calc(i, stack[pw]); if (i < n) { while (last > 0 && !insert_ok(dp[i], data[i+1][0])) --last; stack_insert(i, dp[i], data[i+1][0]); } } answer = dp[n]; return 0; } int output() { FILE *fout = fopen(OUT_FILE_NAME, "w"); fprintf(fout, "%lld\n", answer); fclose(fout); return 0; } int main() { input(); work_init(); work_dp(); output(); return 0; } |