题目链接:https://codeforces.com/problemset/problem/698/C
题目大意:有n种节目,每个节目出现的概率为$p_i$,电视的缓存大小为V,当缓存满了后会挤掉出现次数最少的节目,问在$10^{100}$次询问后每个节目存在缓存中的概率。
Examples
Input
3 1
0.3 0.2 0.5
Output
0.3 0.2 0.5
Input
2 1
0.0 1.0
Output
0.0 1.0
Input
3 2
0.3 0.2 0.5
Output
0.675 0.4857142857142857 0.8392857142857143
Input
3 3
0.2 0.3 0.5
Output
1.0 1.0 1.0
由于n非常小,所以我们可以直接用二进制枚举缓存中节目的状态,假设dp[sta]表示缓存中存储状态为sta的概率,那么其中第i个1的存在表示第i个节目存在缓存中,我们只需要将每个位数为1的地方做过累加就是每位的最终概率了。
由于$10^{100}$非常大,所以当n个节目中存在非0概率的个数大于缓存容量时缓存一定会爆,而小于等于的时候不会爆,所以每个非零概率的节目都会存在于缓存中
所以我们特判一下小于等于的情况,之后就是对爆的情况做状压。
我们枚举状态sta,当sta中1的个数等于缓存容量的时候就是最终状态了,我们就可以做答案处理,如果sta中1的个数小于缓存容量,那么我们就需要进行状态转移了。也就是对sta中0的元素进行转移即:
for (int j=1; j<=n; j++) if ((i&(1<<(j-1)))==0) dp[i|(1<<(j-1))]+=dp[i]*p[j]/zros;//有zros份可以补充,其中j占据了p[j]份
也就是对该位的0进行补充,但需要注意的是sta有很多个0要补充,所以我们要将这所有的0位置的概率做个累加记为zros,那么补充该位置的0的概率就是p[j]/zros
那么答案也就很容易出来了
以下是AC代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const double esp=1e-8; #define debug printf("@#$#@$#@%#@1^&^**@#$#@%))#% " ) double p[30],dp[1<<21],ans[30]; int main() { int n,v,cnt=0; scanf ("%d%d",&n,&v); for (int i=1; i<=n; i++){ scanf ("%lf",&p[i]); if (p[i]>=esp) cnt++; } if (cnt<=v) { for (int i=1; i<=n; i++) printf("%.6f%c",p[i]<esp?0.0:1.0,i==n?' ':' '); return 0; } dp[0]=1; for (int i=0; i<(1<<n); i++){ int use=0; double zros=0; for (int j=1; j<=n; j++){ if (i&(1<<(j-1))) use++; else zros+=p[j]; } if (use>v) continue; else if (use==v){ for (int j=1; j<=n; j++) if (i&(1<<(j-1))) ans[j]+=dp[i]; } else { for (int j=1; j<=n; j++) if ((i&(1<<(j-1)))==0) dp[i|(1<<(j-1))]+=dp[i]*p[j]/zros;//有zros份可以补充,其中j占据了p[j]份 } } for (int i=1; i<=n; i++) printf("%.10f%c",ans[i],i==n?' ':' '); return 0; }