【CF 698C】 LRU(概率DP)

【CF 698C】 LRU(概率DP)

C. LRU
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

While creating high loaded systems one should pay a special attention to caching. This problem will be about one of the most popular caching algorithms called LRU (Least Recently Used).

Suppose the cache may store no more than k objects. At the beginning of the workflow the cache is empty. When some object is queried we check if it is present in the cache and move it here if it's not. If there are more than k objects in the cache after this, the least recently used one should be removed. In other words, we remove the object that has the smallest time of the last query.

Consider there are n videos being stored on the server, all of the same size. Cache can store no more than k videos and caching algorithm described above is applied. We know that any time a user enters the server he pick the video i with probability pi. The choice of the video is independent to any events before.

The goal of this problem is to count for each of the videos the probability it will be present in the cache after 10100 queries.

Input

The first line of the input contains two integers n and k (1 ≤ k ≤ n ≤ 20) — the number of videos and the size of the cache respectively. Next line contains n real numbers pi (0 ≤ pi ≤ 1), each of them is given with no more than two digits after decimal point.

It's guaranteed that the sum of all pi is equal to 1.

Output

Print n real numbers, the i-th of them should be equal to the probability that the i-th video will be present in the cache after 10100queries. You answer will be considered correct if its absolute or relative error does not exceed 10 - 6.

Namely: let's assume that your answer is a, and the answer of the jury is b. The checker program will consider your answer correct, if .

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 

题目大意:cache高速缓存,有n个程序,已知缓存可容纳最多k个程序。

缓存的存储规则如下:

初始缓存中程序为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;
}



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值