题目:
http://codeforces.com/problemset/problem/698/C
有一个大小为 k k k的缓冲队列,每次从 n n n种物品中按照一定的概率选取一种物品尝试放进去,第 i i i个物品被选中的概率为 p i p_i pi。如果这种物品已经在队列就不用管,如果不在就放入队尾。如果队列满了就把队首弹出。问 1 0 100 10^{100} 10100次后每个物品在队列中的概率。
思路:
1
0
100
→
∞
10^{100}\rightarrow \infty
10100→∞,也就是说,做很多很多次之后,缓存区一定是满的,缓存区不满的概率趋近于
0
0
0。
像这种题肯定不能正着想,因为可能会放进去又弹出来,而且物品最后在队列中和前面的操作没有太大关系,主要看后面的操作。所以我们只需要计算出最后缓冲队列满的情况的概率(因为如果后面的操作能使队列满,那么前面怎么放都是满的),相当于从最后开始放物品,每一次选一种加入,加满就停止的概率。
设
f
(
i
)
f(i)
f(i)为
i
i
i的二进制下每个物品是否在队列中的状态的概率,
P
P
P表示在
i
i
i状态下选到的物品已经在队列中的概率,
j
j
j表示下一个将要被放到队列中的物品,用
f
(
i
)
f(i)
f(i)更新
f
(
i
∣
(
1
<
<
j
−
1
)
)
f(i|(1<<j-1))
f(i∣(1<<j−1))
f
(
i
∣
(
1
<
<
j
−
1
)
)
=
∑
k
=
0
∞
P
k
∗
p
j
∗
f
(
i
)
f(i|(1<<j-1))=\sum_{k=0}^{\infty}P^k*p_j*f(i)
f(i∣(1<<j−1))=k=0∑∞Pk∗pj∗f(i)
解释就是,在
i
i
i状态下,可能还要连续选中
k
(
0
≤
k
≤
∞
)
k(0\le k \le \infty)
k(0≤k≤∞)个已经在队列中的物品然后才选到第
j
j
j个物品。
∑
k
=
0
∞
P
k
=
1
−
P
∞
1
−
P
→
1
1
−
P
\sum_{k=0}^{\infty}P^k=\frac{1-P^{\infty}}{1-P}\rightarrow \frac{1}{1-P}
∑k=0∞Pk=1−P1−P∞→1−P1
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 25
double eps=1e-12;
int n,m,k;
double p[N],ans[N],cnt[1<<21],f[1<<21];
int main()
{
scanf("%d%d",&n,&k);m=n;
for (int i=0;i<n;++i)
{
scanf("%lf",&p[i]);
if (p[i]<eps) --m;
}k=min(k,m);
f[0]=1;
for (int i=0;i<1<<n;++i)
{
int cnt=0;double now=0;
for (int j=0;j<n;++j)
if ((i>>j)&1) ++cnt,now+=p[j];
if (cnt==k)
{
for (int j=0;j<n;++j)
if ((i>>j)&1) ans[j]+=f[i];//要选满才算
continue;
}
if (cnt>k) continue;
f[i]/=1-now;
for (int j=0;j<n;++j)
if (!((i>>j)&1)) f[i|(1<<j)]+=f[i]*p[j];
}
for (int i=0;i<n;++i) printf("%.16lf%c",ans[i]," \n"[i==n-1]);
}