[HEOI2015]小L的白日夢
本文參考了yyb大神的題解,並且加入了一些自己的看法
三個性質都可以和暴力拍上,所以應該是正確的
性質1:一定存在最優解每天不高興的概率是單調不增的
看著比較顯然
證明也比較容易,首先按不高興概率單調不增把每個專案排序,說人話就是令\(a_i\ge a_{i+1}\)
根據期望線性性,當前期望為\(E=\sum\limits_{i=1}^n(1-a_{i-1})a_i\)
考慮類似貪心的臨項交換法,假設兩數在原數列位置是\(i,j\),且\(i<j\),交換,令原式與交換後式子做差
\[\Delta=(1-a_{i-1})a_i+(1-a_i)a_{i+1}+(1-a_{j-1})a_j+(1-a_j)a_{j+1}\\ -(1-a_{i-1})a_j-(1-a_j)a_{i+1}-(1-a_{j-1})-(1-a_i)a_{j+1}\\ \]化簡式子
\[=(a_i-a_j)(a_{j-1}-a_{i-1})+(a_{i+1}-a_{j+1})(a_j-a_i)\\ =(a_i-a_j)(a_{j-1}+a_{j+1}-a_{i-1}-a_{i+1}) \]因為有\(a_{i-1}>a_i>a_{i+1}>a_{j-1}>a_j>a_{j+1}\)
所以有\(\Delta<0\),所以序列單調不增期望最小
性質2:選擇的一定是排序後的一段字首和一段字尾
不妨設\(i<j<k<l\)表示四個專案,它們不高興概率為\(a>b>c>d\)
假設我們選擇的原情況是選了\([1,i],j,[l,n]\)
新情況是選了\([1,i],k,[l,n]\),期望是\((1-a)c+(1-c)d\)
新情況優於原情況時,滿足\((1-a)b+(1-b)d>(1-a)c+(1-c)d\)
解得\(a+d>1\),也就是說,中間點會靠到字尾上,反之靠到字首上
綜上,字首和字尾中間不會有點被選中
先用這兩個性質做,我們可以預處理字首和字尾最大貢獻,列舉字首端點,對於所有後綴而言,找一個最大的貢獻即可,這樣可以做\(1e6\),但是做不了\(1e9\)
性質3:每個東西要麼選一個,要麼全選,除了這兩種情況的其它情況最多隻出現一次
首先沒選完整的最多隻可能有兩塊,字首的最後和字尾的最前
考慮字尾最靠前的一段多出來了若干個,把一個 字尾的多餘 給 字首最後一個,期望值減少量是\(\Delta\),更優,我們不斷減少字尾,直到字尾第一個塊只剩一個點,再次削減肯定代價不再是\(\Delta\),所以不能轉移了
(根據上面式子可以比較容易得出)
#include <bits/stdc++.h>
using namespace std;
typedef long long lng;
typedef long double ldb;
struct data {
int cnt;
ldb val;
inline data() {};
inline data(int a, ldb b)
: cnt(a), val(b) {};
inline void read() {
static int a, b;
scanf("%d/%d", &a, &b);
val = (ldb)a / b;
scanf("%d", &cnt);
}
} A[150000], B[350000];
inline bool operator < (const data &a, const data &b) {
return a.val > b.val;
}
int n, m, cas, tot;
inline ldb calc() {
ldb ret = 1E18, sum = 0;
lng now = 1, rem = m;
for (int i = n; i; --i)
sum += (B[i].cnt - 1) * B[i].val * (1 - B[i].val) + (1 - B[i].val) * B[i + 1].val, rem -= B[i].cnt;
for (int i = 1; i <= n; ++i ) {
rem -= B[i].cnt;
while (now <= n && rem <= 0)
sum -= (B[now].cnt - 1) * B[now].val * (1 - B[now].val) + (1 - B[now].val) * B[now + 1].val, rem += B[now++].cnt;
if (rem <= 0)break;
sum += (B[i].cnt - 1) * B[i].val * (1 - B[i].val) + (1 - B[i - 1].val) * B[i].val;
ret = min(ret, sum + (rem - 1) * B[now - 1].val * (1 - B[now - 1].val) + (1 - B[now - 1].val) * B[now].val + (1 - B[i].val) * B[now - 1].val);
}
rem = m, sum = 0;
for (int i = 1; i <= n; ++i) {
int mn = min(rem, (lng)B[i].cnt);
if(!mn)break;
else rem -= mn, sum += (mn - 1) * B[i].val * (1 - B[i].val) + (1 - B[i - 1].val) * B[i].val;
}
return ret = min(ret, sum);
}
signed main() {
for (scanf("%d", &cas); cas--; ) {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
A[i].read();
if (!A[i].cnt)--i, --n;
}
sort(A + 1, A + n + 1);
tot = 0;
for (int i = 1; i <= n; ++i) {
B[++tot] = data(1, A[i].val);
if (--A[i].cnt) {
if (A[i].cnt > 1)
B[++tot] = data(A[i].cnt - 1, A[i].val);
B[++tot] = data(1, A[i].val);
}
}
B[0].val = 1, B[(n = tot) + 1].val = 0;
ldb ans = calc();
for (int i = 1; i <= n; ++i)
if (i < n + 1 - i)swap(B[i], B[n + 1 - i]);
for (int i = 1; i <= n; ++i)B[i].val = 1 - B[i].val;
ans = min(ans, calc());
printf("%.6lf\n", (double)fabs(ans));
}
}