1. 程式人生 > 其它 >B. Glass Half Spilled_思維+揹包

B. Glass Half Spilled_思維+揹包

B. Glass Half Spilled_思維+揹包

2000

題目大意

給n個水杯,每個杯子有不同的最大容量ai和現在的水量bi 。每次可以選擇任意一個杯子,把該杯子裡的水倒x單位到另外一個杯子裡。但是這些杯子很奇怪,倒出x單位的水,倒到另外一個杯子裡只有x/2單位(浮點數)。現問經過若干次操作後,i杯水的和的最大值是多少(i屬於1~n)

思路和程式碼

首先,考慮三個杯子。a杯的水倒x單位到b中只剩下x/2單位。如果我們要把b中的水再倒到c杯子,那麼就只有x/4單位。這樣的話不如直接從a杯倒到c杯。所以,每一份水最多倒一次。

再考慮,如果我們選定了i個杯子,那麼最好的情況就是把其餘n-i個杯子裡的水全部倒進這i個杯子裡。(溢位也沒事)

所以不妨設dp[i,j,k]為前i個杯子裡選擇j個,且這j個杯子的容量為k的最大水量和。

可以發現這是一個簡單的01揹包,所以可以將第一維i優化掉。

最後的答案就是選擇i個杯子,然後將其餘杯子裡的水倒到這i個杯子裡。
$$
ans=dp_{i,j}+(sum-dp_{i,j})/2
$$
但是我設定的陣列是整數,所以稍微改了一下,把上面式子乘了2再化簡就得到:
$$
ans*2=sum+dp_{i,j}
$$

/*
一個杯子分出去的水不會再被分到另外一個杯子
dp[i,j,k]表示前i個杯子中選擇j個,總容量為k,可以容納的最大水量 

杯子有容量限制
倒水只能倒一半 

如果有選擇了j個杯子,那麼就是要把n-j個杯子裡的水倒到j個裡面去。
而且根據上面的分析,每個杯子只會倒水一次 
now[j] = min(Vj , now[j] + now[n-j]/2) 

所以要知道選擇了哪幾個,這樣的話複雜度會到2e100(並不需要)

將總水量隨著dp轉移可以維護Vj (並不需要)
*/
//以上註釋是我做題時的思維過程,不是正解
void solve(){
	cin >> n ; 
	
	vct<ll> a(n + 1 , 0) ;
	vct<ll> b(n + 1 , 0) ;

	vct<vct<ll> > dp(n + 1 , vct<ll>(10010 , -INF)) ;
	dp[0][0] = 0 ;
	rep(i , 1 , n) cin >> a[i] >> b[i] ;

	ll sum = accumulate(b.begin() + 1 , b.end() , 0LL) ;
	
	rep(i , 1 , n)
	drep(j , 1 , i)
	drep(k , a[i] , 10001)
	dp[j][k] = max(dp[j][k] , dp[j - 1][k - a[i]] + b[i]) ;
	
	rep(i , 1 , n){
		ll ans = 0 ;
		rep(j , 1 , 10001)
		ans = max(ans , min(2LL * j , sum + dp[i][j])) ;
		cout << fixed << setprecision(10) << 1.0 * ans / 2 << " " ;
	}
	
}//code_by_tyrii 

小結

揹包模型的靈活轉化