[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\),交换,令原式与交换后式子做差
化简式子
因为有\(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-a)b+(1-b)d\)
新情况是选了\([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));
}
}