POJ2976 Dropping tests 題解 01分數規劃

題目鏈接:http://poj.org/problem?id=2976

題目大意

在某門課程中,你要進行 \(n\) 個測試。其中第 \(i\) 個測試一共有 \(b_i\) 道問題,你答對的有 \(a_i\) 道。你在這門課程中的成績定義爲

\[100 \cdot \frac{\sum_{i=1}^{n} a_i}{\sum_{i=1}^{n} b_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\}\) 最大化

\[\frac{\sum\limits_{i=1}^{n} a_i \times w_i}{\sum\limits_{i=1}^{n} b_i \times w_i} \]

這裏還有一個限制是 \(\sum\limits_{i=1}^n w_i \ge n-k\)

我們如果當前的 \(mid\) 滿足條件則必然

\[\frac{\sum a_i \times w_i}{\sum b_i \times w_i} \ge mid \]

\[\Rightarrow \sum a_i \times w_i - mid \times \sum b_i \times w_i \ge 0 \]

\[\Rightarrow \sum w_i \times (a_i - mid \times b_i) \ge 0 \]

爲了讓上述條件滿足,我會選 \(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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章