[HDU4336]:Card Collector(概率DP)

题目传送门


题目描述

夏川的生日就要到了。作为夏川形式上的男朋友,季堂打算给夏川买一些生日礼物。
商店里一共有种礼物。夏川每得到一种礼物,就会获得相应喜悦值$W_i$(每种礼物的喜悦值不能重复获得)。
每次,店员会按照一定的概率$P_i$(或者不拿出礼物),将第i种礼物拿出来。季堂每次都会将店员拿出来的礼物买下来。没有拿出来视为什么都没有买到,也算一次购买。
众所周知,白毛切开都是黑的。所以季堂希望最后夏川的喜悦值尽可能地高。
求夏川最后最大的喜悦值是多少,并求出使夏川得到这个喜悦值,季堂的期望购买次数。


输入格式

第一行,一个整数N,表示有N种礼物。
接下来N行,每行一个实数$P_i$和正整数$W_i$,表示第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<{W}_{i}≤{10}^{9}$0<${P}_{i}$≤1且$\sum \limits_{i=1}^{n}{P}_{i}≤1$


题意解释

又是血淋淋的没看懂题,再次印证了得语文者得OI,售货员一次只可能拿出一个礼物,且每个礼物只能买一次(捂脸……)


题解

偷偷看了看数据范围,喜悦值没有负值,于是乎,第一问就解决了,肯定是把所有礼物的喜悦值加起来。

那么来考虑10%的算法,N=1,我们只需要把这件礼物买下,且这件礼物的期望购买次数是$\frac{1}{P_i}$

再来考虑30%的算法,那么每种商品都有买或不买两种状态,那么共有$2^N$种状态,$2^{{N}^{3}}$刚好跑过。

状态可以用状压(撞鸭)DP:设dp[S]表示当前状态为S,到买到所有礼物的期望步数。

剩下的那个$N^3$算法很容易想到高斯消元。

那么我们可以写出式子:

$dp[S]=\sum \limits_{i=1}^N P_i \times dp[S']+(1-\sum \limits_{i=1}^N P_i) \times dp[S]+1$

其中,S表示当前状态,S'为上一个状态,i表示没有买过的物品。

写出这个式子,你就跟正解不远了,把式子移个项:

$\sum \limits_{i=1}^N P_i \times dp[S]=\sum \limits_{i=1}^N P_i \times dp[S']+1\\dp[S]=\frac{\sum \limits_{i=1}^N P_i \times dp[S']+1}{\sum \limits_{i=1}^N P_i}$

这就是我们的DP式子啦,时间复杂度$O(2^N)$

注意HDU输入格式不同,而且是special judge,记得输出时多保留一位即可(跟HDU数据刚了一下午,才发现是special judge心态瞬间爆炸……)


代码时刻

#include<bits/stdc++.h>
using namespace std;
int n;
double p[21];
long long w[21];//记得开long long
double dp[2000000];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lf%lld",&p[i],&w[i]);
		p[0]+=p[i];
		w[0]+=w[i];
	}
	printf("%lld\n",w[0]);//第一问
	for(int i=(1<<n)-2;i>=0;i--)//dp
	{
		double flag=0.0;//分母
		for(int j=1;j<=n;j++)
		{
			if((1<<(j-1))&i)continue;
			dp[i]+=p[j]*dp[i|1<<(j-1)];
			flag+=p[j];
		}
		dp[i]=(dp[i]+1.0)/flag;
	}
	cout<<fixed<<setprecision(3)<<dp[0]<<endl;//保留小数输出第二问
	return 0;
}

HDU代码:

#include<bits/stdc++.h>
using namespace std;
int n;
double p[21];
double dp[2000000];
int main()
{
    while(~scanf("%lld",&n))
    {
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
            scanf("%lf",&p[i]);
        for(int i=(1<<n)-2;i>=0;i--)
        {
            double flag=0.0;
            for(int j=1;j<=n;j++)
            {
                if((1<<(j-1))&i)continue;
                dp[i]+=p[j]*dp[i|1<<(j-1)];
                flag+=p[j];
            }
            dp[i]=(dp[i]+1.0)/flag;
        }
        cout<<fixed<<setprecision(4)<<dp[0]<<endl;//注意HDU是special judge
    }
    return 0;
}

rp++

posted @ 2019-07-16 14:37  HEOI-动动  阅读(253)  评论(0编辑  收藏  举报