codeforces 699e LRU
题目链接
题意
给你n个视屏和一个大小为k的缓存区,每次选取一个视屏询问,如果视屏不在缓存区中,则加入缓存区,如果缓存区中视屏超过k个,则删除最早询问的视屏。给你每个视屏被询问的概率,每个视屏被选取的概率独立,问在10^100询问后每个视屏仍在缓存区内的概率。
思路
考虑到只有最后仍在缓存区中的视屏只有k个,所以对结果有影响的只有最后k次选取。
即前10^100 - k次选取的所有可能取法都对最后的结果没影响,所以在他们所有取法的基础上取就行了。
也就是说从后往前取,每当取到一个没有出现过的就加入集合,直到取满k个。
假如取到了重复的视屏,对集合的个数变化没有影响,所以可以看做是把这次取视屏与之前某次取不同视屏的操作交换,因为操作是独立的,所以不影响结果。
换言之,不需要考虑重复取的情况,直接算每次不同的转移即可。剩下的直接状压dp即可。
代码
#include <string.h>
#include <stdio.h>
double p[22],d[(1<<20) + 10];
double ans[22];
int hmany(int a){
int ret = 0;
while(a){
if(a%2) ret ++;
a >>= 1;
}
return ret;
}
int n,k;
int main(){
scanf("%d%d",&n,&k);
int num = 0;
for(int i = 1;i <= n;i ++) {
scanf("%lf",&p[i]);
if(p[i] == 0) num ++;
}
if(n - num < k) k = n - num;
memset(d,0,sizeof(d));
d[0] = 1.0;
for(int i = 0;i <(1<< n);i ++){
int tmp = i;
double tmpp = 0.0;
int tmpk = 0;
for(int j = 1;j <= n;j ++){
if(i & (1 << (j - 1))){
tmpk ++;
}
else tmpp += p[j];
}
if(tmpk >= k) continue;
for(int j = 1;j <= n;j ++){
if(((1 << (j - 1)) & i )== 0){
d[i | (1 << (j - 1))] += d[i] * (p[j]/tmpp);
}
}
}
memset(ans,0,sizeof(ans));
for(int i = 0;i < (1 << n);i ++){
if(hmany(i) != k) continue;
for(int j = 1;j <= n;j ++){
if((1 << (j - 1)) & i){
ans[j] += d[i];
}
}
}
for(int i = 1;i <= n; i ++){
printf("%.10lf\n",ans[i] );
}
return 0;
}