[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-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));
	}
}
posted @ 2020-10-25 18:43  INFP  阅读(92)  评论(0编辑  收藏  举报