1. 程式人生 > 實用技巧 >禮物(概率dp)

禮物(概率dp)

題目大意

夏川的生日就要到了。作為夏川形式上的男朋友,季堂打算給夏川買一些生 日禮物。
商店裡一共有種禮物。夏川每得到一種禮物,就會獲得相應喜悅值Wi(每種禮物的喜悅值不能重複獲得)。
每次,店員會按照一定的概率Pi(或者不拿出禮物),將第i種禮物拿出來。 季堂每次都會將店員拿出來的禮物買下來。沒有拿出來視為什麼都沒有買到,也算一次購買。
眾所周知,白毛切開都是黑的。所以季堂希望最後夏川的喜悅值儘可能地高。
求夏川最後最大的喜悅值是多少,並求出使夏川得到這個喜悅值,季堂的期望購買次數。

輸入格式

第一行,一個整數N,表示有N種禮物。
接下來N行,每行一個實數Pi和正整數Wi,表示第i種禮物被拿出來的概率和 可以獲得喜悅值。

輸出格式

第一行,一個整數表示可以獲得的最大喜悅值。
第二行,一個實數表示獲得這個喜悅值的期望購買次數,保留3位小數。

資料範圍

對於10%的資料,N = 1
對於30%的資料,N ≤ 5
對於100%的資料,N ≤ 20,0 < Wi ≤ 10^9 ,0 < Pi ≤ 1且∑Pi ≤ 1
注意:本題不設spj

演算法分析

  • N最大隻有20 所以顯然可以用狀壓來做
  • 若我們用1來表示買了 0表示未買 則我們照樣選擇倒著列舉 然後舉例說明
    10101這樣一個狀態 再買一個東西可以轉移到 10111 或者 11101兩種
    買到第二個位置的概率 乘上之前已經求過的 10111 的dp值 然後把每個0的位置都列舉一遍 求和 再加上( 1 - 所有0位置的概率和)(這是買空或者買到買過的東西的概率) × dp[當前狀態] 就好了
  • 所以我們轉移方程寫出來應該是這個樣子的 f[i] = \(\sum\) (dp[j] * p[k]) + (1 - \(\sum\)p[k]) * f[i] + 1
    移項之後可以得到 f[i] = (\(\sum\) (dp[j] * p[k]) + 1) \(\div\) (\(\sum\)p[k])
    i 表示當前的狀態 j表示當前狀態再買一個之前沒買過的物品的後的可能狀態 k表示買到那一個物品的概率

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 25;
const int Max = 1 << 20;
double p[maxn];
double f[Max];
int w[maxn];
ll sum;


int main(){
	int n;scanf("%d",&n);
	for(int i = 1;i <= n;++i){
		int x;scanf("%lf%d",&p[i],&x);
		sum += x;
	}
	int S = (1 << n) - 2;
	for(int i = S;i >= 0;--i){
		double sump = 0;//$\sum$p[k]
		double sum = 0;//$\sum$ (dp[j] * p[k])
		for(int j = 1;j <= n;++j){//列舉買了第幾個物品
			int cur = 1 << (j-1);
			if(cur & i)continue;//這個物品是否已經買過
			sum += p[j] * f[cur | i];//f[cur|i] 一定在之前的時候求過了
			sump += p[j];//讓買到新物品的概率累加
		}
		f[i] = (sum + 1) * 1.0 / sump;//剛才推導的式子
	}
	printf("%lld\n%.3lf\n",sum,f[0]);
	return 0;
}