1. 程式人生 > 實用技巧 >[HEOI2015]小L的白日夢

[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));
	}
}