• 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
  • 相关阅读:
    pytroch resnet构建过程理解
    python 参数前星号(*)的用法
    Win10 + Visual Studio 2017 下 OpenCV无法显示图像的问题
    模板类中的友元函数
    caltech行人检测数据集上的论文
    (转)使用K-S检验一个数列是否服从正态分布、两个数列是否服从相同的分布
    matplotlib较好的博客
    Python 获取时间戳
    (转载)keras使用入门及3D卷积神经网络资源
    linux 下的字典安装
  • 原文地址:https://www.cnblogs.com/Juve/p/11195245.html
Copyright © 2020-2023  润新知