0.題目
題目描述
幾個人一起出去吃飯是常有的事。但在結帳的時候,常常會出現一些爭執。
現在有 \(n\) 個人出去吃飯,他們總共消費了 \(S\) 元。其中第 \(i\) 個人帶了 \(a_i\) 元。幸運的是,所有人帶的錢的總數是足夠付賬的,但現在問題來了:每個人分別要出多少錢呢?
為了公平起見,我們希望在總付錢量恰好為 \(S\) 的前提下,最後每個人付的錢的標準差最小。這裡我們約定,每個人支付的錢數可以是任意非負實數,即可以不是 \(1\) 分錢的整數倍。你需要輸出最小的標準差是多少。
標準差的介紹:標準差是多個數與它們平均數差值的平方平均數,一般用於刻畫這些數之間的“偏差有多大”。形式化地說,設第 \(i\) 個人付的錢為 \(b_i\) 元,那麼標準差為 \(s=\sqrt{\frac{1}{n}\sum_{i=1}^n(b_i-\frac{1}{n}\sum_{i=1}^n b_i)}\)
輸入格式
第一行包含兩個整數 \(n\)、\(S\);
第二行包含 \(n\) 個非負整數 \(a_1,\cdots,a_n\)。
輸出格式
輸出到標準輸出。
輸出最小的標準差,四捨五入保留 \(4\) 位小數。
保證正確答案在加上或減去 \(10^{-9}\) 後不會導致四捨五入的結果發生變化。
樣例 #1
樣例輸入 #1
5 2333
666 666 666 666 666
樣例輸出 #1
0.0000
樣例 #2
樣例輸入 #2
10 30
2 1 4 7 4 8 3 6 4 7
樣例輸出 #2
0.7928
提示
【樣例解釋】
- 每個人都出 2333/5 元,標準差為 0。
【資料約定】
對於 \(10\%\) 的資料,所有 \(a_i\) 相等;
對於 \(30\%\) 的資料,所有非 \(0\) 的 \(a_i\) 相等;
對於 \(60\%\) 的資料,\(n \le 1000\);
對於 \(80\%\) 的資料,\(n \le 10^5\);
對於所有資料,\(n \le 5 \times 10^5,0 \le a_i \le 10^9\)。
1.題解
1.1 貪心
思路
這裡由於付錢總款固定, 人數固定, 所以平均值是固定的
總體思路使用到貪心的思想, 在儘可能使得每一步中每個人付的錢在後續足夠的情況下,儘可能的少.
前面的不夠(小於第一次計算的avg)後面的補上, 但當後面人總錢數大於(avg)的時候,我們就要考慮補多少
我們就可以考慮 還需要補的總錢數 / 剩餘人數 = cur_avg(此種情況標準差最小);
可以這麼思考, 我們在付完所有錢少於avg的人後, 如果我們希望剩餘所有錢大於avg的人標準差最小, 有公式:
\((y_i - cur\_avg)^2 = (y_i - avg + avg - cur\_avg)^2 = (y_i - avg)^2 + 2(y_i - avg)(avg - cur\_avg) + (avg - cur\_avg)^2\)
我們可以發現(avg-cur_avg)是一個固定值且大於零, 又因為 \(y_i > avg, avg - cur\_avg < 0\),
所以當所有 \((y_i - cur\_avg)^2\) 和最小的時候, 也能得到所有 \((y_i - avg)^2\) 和最小
我們畫個圖就能明白, 設常數 a = cur_avg - avg, 當(範圍: x = y_i - avg > 0 ) (x-a)^2 和 x^2 單調性是一樣的, 在 (x-a)^2 取的最小值時, x^2 也能取到最小值
若是當前人所持錢少於cur_avg, 那麼就進一步計算新的cur_avg, 直到當前人錢大於當前cur_avg, 後面所有人均付cur_avg即可
我們可以簡單分析一下,若能讓每個人在夠付賬的情況下付的金額儘可能集中就可以使得標準差最小。
舉個例子假如n = 10 , S = 30,每個人金額分別為1 , 2 , 3 , 4 , 4 , 4 , 6 , 7 , 7 , 8
先求出均值avg = 4.6,再對金額進行排序。
1.當金額小於均值時需要付出所有的錢;
2.當金額大於或等於均值時,這個人以及之後的所有人都需要在第一步完成的基礎上重新計算均值即為cur_avg
程式碼
#include<bits/stdc++.h>
#define ll long long
const int M = 5e5 + 1;
ll a[M];
using namespace std;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n;
ll S;
cin >> n >> S;
for(int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a+1, a+n+1);
double avg = (S * 1.0) / n, differ = 0;
for(int i = 1; i <= n; i++) {
// 錢夠了,全部支付
if (a[i] * (n - i + 1) >= S) {
double remain_avg = S * 1.0 / (n-i+1);
differ += (n - i + 1) * pow(remain_avg - avg, 2);
break;
} else {
S -= a[i];
differ += pow(a[i] - avg, 2);
}
}
differ = sqrt(differ / n);
printf("%.4lf", differ);
return 0;
}