1. 程式人生 > 實用技巧 >codeforces 1459D. Glass Half Spilled

codeforces 1459D. Glass Half Spilled

題面傳送門


首先這場比賽是真的爽,33分鐘寫完了三道題(雖然剩下的一個半點一道沒搞出來),排名140多,漲了133rating.


好了迴歸正題,這題確實挺難的(雖然tourist8分鐘切掉了),因為他有兩個思維階段。


我們記選的\(k\)個杯子的集合為\(S\),總容量為\(A_S\),含有的水為\(B_S\)。那麼我們能得到最多的水是\(min \{ A_S, B_S+\frac{B-B_S}{2} \}\)。因為分多次往一個杯子裡倒水或者一個杯子往不同的杯子裡倒水和一次性的把水都倒在一個杯子裡是一樣的(這個用小學數學就能證明)。
那麼現在的問題是如何選取集合\(S\),使\(B_S\)

最大化。


因為每一個杯子只有選和不選兩種,因此可以用揹包試一試:
\(a_i\)看成體積,\(b_i\)看成價值,那麼有\(dp[i][A]\),表示在前\(i\)個杯子中,總體積為\(A\)時得到的最大\(B_S\)。又因為題目要選剛好\(k\)個,所以我們就再加一維:\(dp[i][k][A]\),表示在前\(i\)個杯子中,選了\(k\)個,總容量為\(A\)時的最大\(B_S\)
轉移就呼之欲出了:\(dp[i][k][A] = max \{dp[i-1][k][A], dp[i-1][k-1][A-a_i]+b_i \}\).


這就是題目資料範圍為什麼給了那麼小的原因,而且還限制了\(a_i\)
。時間複雜度\(O(n^3A)\)\((A=max \{a_i \})\),空間複雜度需要仿照01揹包將第一維優化掉,得到\(O(n^3)\)


沒想到一道感覺相當複雜的題考的竟然是01揹包,真的很喜歡這種知識本身不難但是需要大量思考的題。
(話說題面要保證誤差不超過\(10^{-9}\)是什麼鬼,果然是來坑想我這樣的菜雞的)

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<queue>
#include<assert.h>
#include<ctime>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 105;
In ll read()
{
	ll ans = 0;
	char ch = getchar(), las = ' ';
	while(!isdigit(ch)) las = ch, ch = getchar();
	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
	if(las == '-') ans = -ans;
	return ans;
}
In void write(ll x)
{
	if(x < 0) x = -x, putchar('-');
	if(x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}

int n, a[maxn], b[maxn];
int dp[maxn][maxn * maxn];

int main()
{
	n = read();
	int sa = 0, sb = 0;
	for(int i = 1; i <= n; ++i)
	{
		a[i] = read(), b[i] = read();
		sa += a[i], sb += b[i];
	} 
	Mem(dp, -1), dp[0][0] = 0;
	for(int i = 1; i <= n; ++i)
		for(int k = i; k; --k)
			for(int A = sa; A >= a[i]; --A)
				if(~dp[k - 1][A - a[i]]) dp[k][A] = max(dp[k][A], dp[k - 1][A - a[i]] + b[i]);
	for(int i = 1; i <= n; ++i)
	{
		db tp = 0;
		for(int j = 0; j <= sa; ++j) 
			if(~dp[i][j]) tp = max(tp, min(1.0 * j, 0.5 * (sb + dp[i][j])));
		printf("%.10lf ", tp);
	}
	enter;
	return 0;
}