1. 程式人生 > 實用技巧 >codeforces--698C LRU狀壓+概率DP

codeforces--698C LRU狀壓+概率DP

題目連結: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^&^**@#$#@%))#%\n" ) 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?'\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?'\n':' '); return 0; }