NOIP2016提高A组 A题 礼物—概率状压dp

题目描述

夏川的生日就要到了。作为夏川形式上的男朋友,季堂打算给夏川买一些生 日礼物。

商店里一共有n种礼物。夏川每得到一种礼物,就会获得相应喜悦值Wi(每种礼物的喜悦值不能重复获得)。

每次,店员会按照一定的概率Pi(或者不拿出礼物),将第i种礼物拿出来。 季堂每次都会将店员拿出来的礼物买下来。没有拿出来视为什么都没有买到,也 算一次购买。

众所周知,白毛切开都是黑的。所以季堂希望最后夏川的喜悦值尽可能地高。

求夏川最后最大的喜悦值是多少,并求出使夏川得到这个喜悦值,季堂的期 望购买次数。

输入格式

第一行,一个整数N,表示有N种礼物。

接下来N行,每行一个实数Pi和正整数Wi,表示第i种礼物被拿出来的概率和 可以获得喜悦值。

输出格式

第一行,一个整数表示可以获得的最大喜悦值。

第二行,一个实数表示获得这个喜悦值的期望购买次数,保留3位小数。

样例

样例输入

3
0.1 2
0.2 5
0.3 7

样例输出

14
12.167

数据范围与提示

对于10%的数据,N = 1
对于30%的数据,N ≤ 5
对于100%的数据,N ≤ 20 ,0 < Wi ≤ 10^9 ,0 < Pi ≤ 1且∑Pi ≤ 1

solution:

考场上并没有推出式子。。。

最大喜悦值就是所有Wi的和

对于10%的数据:

这是一个送分点,耐心手玩一下也能发现规律,直接1/pi就好了。

对于30%的数据:

观察到N比较小,可以考虑状压DP。设二进制状态S表示当前有哪些物品已经拿了,然后就可以转移了。具体来说:

fi=$sum$(fj*pk)+(1-$sum$pq)*fi+1;

然后可以列出方程组,用高斯消元求解。

时间复杂度:O(23n)

100%:

其中j状态是i状态的子集,k就是i和j相差的那一位,q是i中有的元素。前半段表示从j状态转移到k状态的期望,后半段表示i状态转移回自己的期望,因为步数多了一步所以+1。等式的两边可以消去f[i],再移项就变成了($sum$p[q])*f[i]=$sum$(f[j]*p[k]),($sum$p[q]是f[i]的系数,f[i]+=p[q]*f[i])这样一来只要枚举每一个状态中的所有1就可以递推出f[(1<<n)-1]了。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define MAXN 21
#define ll long long
using namespace std;
ll n,w[MAXN],tot_w=0,tot_s,m;
double f[1<<21],p[MAXN];
int main(){
	scanf("%lld",&n);
	m=(1<<n)-1;
	for(int i=1;i<=n;i++){
		scanf("%lf %lld",&p[i],&w[i]);
		tot_w+=w[i];
	}
	for(int i=1;i<=m;i++){
		double s=0.0;
		f[i]=1;
		for(int j=1;j<=n;j++){
			if(i&(1<<(j-1))){
				s+=p[n-j+1];
				f[i]+=p[n-j+1]*f[i&(~(1<<(j-1)))];
			}
		}
		f[i]/=s;
	}
	printf("%lld
%0.3lf
",tot_w,f[m]);
	return 0;
}

 ps:好像正着推是错误的,但博主还是正推过了,于是博主又打了一遍倒推的,也过了,但不知道为什么正推是错的

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 double f[1<<21],p[21];
 6 long long ans=0,n,w[21];
 7 int main(){
 8     scanf("%lld",&n);
 9     for(int i=0;i<n;i++){
10         scanf("%lf %lld",&p[i],&w[i]);
11         ans+=w[i];
12     }
13     f[(1<<n)-1]=0;
14     for(int i=(1<<n)-2;i>=0;i--){
15         double sum=0;
16         f[i]=1;
17         for(int j=0;j<n;j++)
18             if(~i&(1<<j)){
19                 f[i]+=p[j]*f[i|(1<<j)];
20                 sum+=p[j];
21             }
22         f[i]/=sum;
23     }
24     printf("%lld
%.3lf
",ans,f[0]);
25     return 0;
26 }
View Code
原文地址:https://www.cnblogs.com/Juve/p/11195245.html