题目大意: 每次购买有 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=∑i∈Sai,p=1−∑i=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=i∈S∑j=0∑∞(fS⊕i+(j+1)×hS⊕i)×(gS⊕i+p)j×ai
令
d
=
g
S
⊕
i
+
p
d=g_{S\oplus i}+p
d=gS⊕i+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=0∞xi=1−x1,∑i=0∞(i+1)xi=(1−x)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=i∈S∑fS⊕i×ai×1−d1+hS⊕i×ai(1−d)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+(i∈S∑ai×fS⊕i)+(1−i∈S∑ai)fS=∑i∈Sai1+(∑i∈Sai×fS⊕i)
代码也比上面的简单:
#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。