缓存的存储规则如下:
初始缓存中程序为0
当需要某程序时,如果不在缓存,便加入缓存。
如果缓存此时已满,则选择最久未被访问过的程序退出缓存。
问访问过10^100次后,缓存中每个程序存在的概率。
赛时想,k必访问次数小很多,这样看最后访问的k个程序(重复访问不统计),可以有很多种组合,对于每种组合中每个程序可以累加上概率。
但具体实现没想好……甚至想到爆搜,不过k!……
正解是状压的概率DP。
1<<20种状态。
由于访问次数10^100很大,可以确定最终缓存器中肯定会有k个不同的程序。因为如果不足k个程序的话,那就说明除了缓存器中的程序,其余程序都没有访问过。
可以求知经过10^100次查询后,这种概率微乎其微。(当然,要提前去掉概率为0的程序)
之后就考虑缓存器中最终包含某k种程序的概率。
这一步其实就相当于往最终的缓存器中添加程序。
从空添加到k,其实就是状压dp的状态转移过程。
此外,每次加入新程序时,往状态i中加入程序j,dp[i|(1<<j)] += dp[i]*p[j]/res
这里res是1-(i状态下存储的程序的概率和),也就是分母是除了已在缓存中的程序外的其余程序存在于缓存的概率。
这样在转移的过程中,每当访问一个状态i,看一下,如果i中恰好包含k个程序,那么就可以把dp[i]统计进i状态下每个进程的答案中了。
代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <map>
#include <stack>
#include <algorithm>
#include <cmath>
#define LL long long
#define Pr pair<int,int>
#define VI vector<int>
using namespace std;
const int INF = 0x3f3f3f3f;
const double eps = 1e-6;
const int msz = 1e5+1;
double ans[20];
double p[20];
double dp[1<<20];
int got(int x)
{
int cnt = 0;
while(x)
{
if(x&1) cnt++;
x >>= 1;
}
return cnt;
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
memset(dp,0,sizeof(dp));
dp[0] = 1;
int tot = (1<<n);
int cnt = 0;
for(int i = 0; i < n; ++i)
{
scanf("%lf",&p[i]);
if(p[i] < eps) cnt++;
}
k = min(k,n-cnt);
memset(ans,0,sizeof(ans));
for(int i = 0; i < tot; ++i)
{
double tmp = 1;
for(int j = 0; j < n; ++j)
{
if(i&(1<<j))
{
tmp -= p[j];
//printf("%d %d %d\n",i,k,got(i));
if(got(i) == k)
{
ans[j] += dp[i];
//printf("%d %f\n",i,dp[i]);
}
}
}
for(int j = 0; j < n; ++j)
{
if(i&(1<<j)) continue;
dp[i|(1<<j)] += dp[i]*p[j]/tmp;
//printf("%d %f\n",i|(1<<j),dp[i|(1<<j)]);
}
}
for(int i = 0; i < n; ++i)
{
if(i) putchar(' ');
printf("%.7f",ans[i]);
}
return 0;
}