USACO

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