hdu #4336.Card Collector 题解

题目传送门

题目大意: 每次购买有 a i a_i ai 的概率得到卡片 i i i,求期望购买几次得到所有卡片。

题解

做法1

好像还没有发现别人写这样的笨b做法。。

考虑一个顺推的dp,令 f S f_S fS 表示已经拥有 S S S 内的卡片,期望需要的步数; h S h_S hS 表示从 f ∅ f_{\empty} f 转移到 f S f_S fS 的概率——你可以看做 所有 得到 S S S 这些卡片的方案 的概率之和; g S = ∑ i ∈ S a i , p = 1 − ∑ i = 1 n a i g_S=\sum_{i\in S}a_i,p=1-\sum_{i=1}^n a_i gS=iSai,p=1i=1nai p p p 相当于购买一次啥都没有买个寂寞的概率。

考虑上一张新得到的卡片是谁,那么有转移:
f S = ∑ i ∈ S ∑ j = 0 ∞ ( f S ⊕ i + ( j + 1 ) × h S ⊕ i ) × ( g S ⊕ i + p ) j × a i f_S=\sum_{i\in S}\sum_{j=0}^{\infty} (f_{S\oplus i}+(j+1)\times h_{S\oplus i})\times (g_{S\oplus i}+p)^j\times a_i fS=iSj=0(fSi+(j+1)×hSi)×(gSi+p)j×ai

d = g S ⊕ i + p d=g_{S\oplus i}+p d=gSi+p,熟练使用生成函数的选手知道,当 x ∈ ( 0 , 1 ) x\in (0,1) x(0,1) 时, ∑ i = 0 ∞ x i = 1 1 − x , ∑ i = 0 ∞ ( i + 1 ) x i = 1 ( 1 − x ) 2 \sum_{i=0}^{\infty} x^i=\dfrac 1 {1-x},\sum_{i=0}^{\infty}(i+1)x^i=\dfrac 1 {(1-x)^2} i=0xi=1x1,i=0(i+1)xi=(1x)21,利用这两个公式推一推上面的式子:
f S = ∑ i ∈ S f S ⊕ i × a i × 1 1 − d + h S ⊕ i × a i 1 ( 1 − d ) 2 \begin{aligned} f_S&=\sum_{i\in S} f_{S\oplus i}\times a_i\times\frac 1 {1-d}+h_{S\oplus i}\times a_i\frac 1 {(1-d)^2} \end{aligned} fS=iSfSi×ai×1d1+hSi×ai(1d)21

这样就做完了,关于 h h h 的递推就看代码吧:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int n,cnt[1<<20];double a[21];
double f[1<<20],g[1<<20],h[1<<20];

int main()
{
	for(int i=0;i<20;i++)cnt[1<<i]=i;
	while(~scanf("%d",&n))
	{
		double p=1;
		for(int i=0;i<n;i++)
			scanf("%lf",&a[i]),p-=a[i];
		for(int S=1;S<(1<<n);S++)
			g[S]=g[S-(S&-S)]+a[cnt[S&-S]],h[S]=0;
		h[0]=1;
		for(int S=0;S<(1<<n);S++){
			double sum=0;
			for(int i=0;i<n;i++)
				if(!(S>>i&1))sum+=a[i];
			for(int i=0;i<n;i++)
				if(!(S>>i&1))h[S|(1<<i)]+=h[S]*a[i]/sum;
				//a[i]/sum就是从S转移到S|(1<<i)的概率
		}
		for(int S=1;S<(1<<n);S++){
			f[S]=0;
			for(int i=0;i<n;i++)if(S>>i&1){
				double d=g[S^(1<<i)]+p;
				f[S]+=f[S^(1<<i)]*a[i]/(1-d)+1.0/(1-d)/(1-d)*a[i]*h[S^(1<<i)];
			}
		}
		printf("%.5lf\n",f[(1<<n)-1]);
	}
}

做法2

这种做法是考虑一种巧妙的倒推dp,也是网上除了做法 3 3 3 之外的主流做法。

f S f_S fS 表示已经拥有 S S S 内的卡片,期望还需要多少次才能集齐所有卡片

转移很简单,考虑一次购买得到了什么:
f S = 1 + ( ∑ i ∉ S a i × f S ⊕ i ) + ( 1 − ∑ i ∉ S a i ) f S f S = 1 + ( ∑ i ∉ S a i × f S ⊕ i ) ∑ i ∉ S a i \begin{aligned} f_S&=1+(\sum_{i\not\in S}a_i\times f_{S\oplus i})+(1-\sum_{i\not\in S}a_i)f_S\\ f_S&=\frac {1+(\sum_{i\not\in S}a_i\times f_{S\oplus i})} {\sum_{i\not\in S}a_i} \end{aligned} fSfS=1+(iSai×fSi)+(1iSai)fS=iSai1+(iSai×fSi)

代码也比上面的简单:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int n;
double a[20],f[1<<20];

int main()
{
	while(~scanf("%d",&n))
	{
		for(int i=0;i<n;i++)scanf("%lf",&a[i]);
		f[(1<<n)-1]=0;
		for(int S=(1<<n)-2;S>=0;S--){
			f[S]=1;double sum=0;
			for(int i=0;i<n;i++)if(!(S>>i&1))
				f[S]+=f[S|(1<<i)]*a[i],sum+=a[i];
			f[S]/=sum;
		}
		printf("%.5lf\n",f[0]);
	}
}

做法3

广为人知的Min-Max容斥做法,看例题 1 1 1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值