題目鏈接:http://poj.org/problem?id=2976
題目大意
在某門課程中,你要進行 \(n\) 個測試。其中第 \(i\) 個測試一共有 \(b_i\) 道問題,你答對的有 \(a_i\) 道。你在這門課程中的成績定義爲
現在給你一個權力,你 最多 可以選擇這 \(n\) 個測試中的 \(k\) 個測試(\(k \lt n\)),並將選擇的測試作廢(也就是說如果你將某一個測試 \(j\) 作廢,則 \(a_j\) 和 \(b_j\) 將不計入上述公式)。
舉個例子,假設你有 \(3\) 個測試,對應的答題情況爲 \(5/5, 0/1, 2/6\)(即 \(a_1=b_1=5,a_2=0,b_2=1,a_3=2,b_3=6\))。如果你不選擇任何測試作廢,則你的成績爲 \(100 \cdot \frac{5+0+2}{5+1+6}=50\);然而,如果你將第 \(3\) 個測試作廢,則你的成績爲 \(100 \cdot \frac{5+0}{5+1} \approx 83.33 \approx 83\)。
輸入格式
輸入包含多組測試數據,每組測試數據包含三行。
每組數據的第一行包含三個整數 \(n\) 和 \(k\)(\(0 \le k \lt n \le 1000\)),第二行包含 \(n\) 個整數,兩兩之間以一個空格分隔,表示 \(a_i\),第三行包含 \(n\) 個整數,兩兩之間以一個空格分隔,表示 \(b_i\)(\(0 \le a_i \le b_i \le 10^9\))。
輸入的最後一行包含兩個整數 \(0\),表示輸入數據的結束。
輸出格式
對於每組測試數據,輸出一個整數,表示在最多將 \(k\) 個測試作廢的情況下你能夠得到的最高成績。因爲成績可能不是一個整數,所以你需要輸出答案四捨五入的整數。
樣例輸入
3 1
5 0 2
5 1 6
4 2
1 2 7 9
5 6 7 9
0 0
樣例輸出
83
100
問題分析
對於這個問題,首先我想到的是貪心去除掉比例(\(\frac{a_i}{b_i}\))最小的 \(k\) 個,然後計算剩下的 \(n-k\) 個對應的結果。但是很快發現了一個反例:
假設 \(n=3,k=1\),\(3\) 件物品分別爲 \(10/10,3/10,1/5\),則:
- 保留三件物品,成績爲 \(100 \cdot \frac{10+3+1}{10+10+5} = 56\)
- 去除第 \(1\) 件物品,成績爲 \(100 \cdot \frac{3+1}{10+5} \approx 26.67 \approx 27\)
- 去除第 \(2\) 件物品,成績爲 \(100 \cdot \frac{10+1}{10+5} \approx 73.33 \approx 73\)
- 去除第 \(3\) 件物品,成績爲 \(100 \cdot \frac{10+3}{10+10} = 65\)
可以發現,第 \(3\) 個測試的 \(\frac{a_i}{b_i}\) 是最小的,但是去除第 \(3\) 個測試後的成績卻不是最高的(而是去除第 \(2\) 個測試後的成績最高),這是因爲大家不是在同一個角度(\(b_i\))上分析的,如果分母都一樣,那麼是沒有問題的,但是分母不一樣就會導致這種貪心策略出現問題。
由此推導出我們接下來要討論的 01分數規劃 的解法。
對於本題,我們可以將其看成如下問題:
求一組 \(w_i \in \{ 0, 1\}\) 最大化
這裏還有一個限制是 \(\sum\limits_{i=1}^n w_i \ge n-k\)
我們如果當前的 \(mid\) 滿足條件則必然
爲了讓上述條件滿足,我會選 \(a_i - mid \times b_i\) 最大的 \(n-k\) 個對應的 \(w_i=1\),其它 \(w_i\) 爲 \(0\)。
這樣,我就能通過二分答案找到最終的結果了。
示例代碼:
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstdlib>
const int maxn = 1010;
int n, k;
double a[maxn], b[maxn], c[maxn];
bool check(double mid) {
for (int i = 0; i < n; i ++) c[i] = a[i] - mid * b[i];
std::sort(c, c+n);
double ans = 0;
for (int i = 1; i <= n-k; i ++) ans += c[n-i];
return ans >= 0;
}
int main() {
while (~scanf("%d%d", &n, &k) && n) {
for (int i = 0; i < n; i ++) scanf("%lf", a+i);
for (int i = 0; i < n; i ++) scanf("%lf", b+i);
double L = 0, R = 1;
while (fabs(R - L) > 1e-4) {
double mid = (L + R) / 2.;
if (check(mid)) L = mid;
else R = mid;
}
printf("%d\n", (int) (100 * L + 0.5));
}
return 0;
}