【CSP】202109-4 收集卡牌
题目大意:
小明抽卡,卡池共有n张卡,每张抽到的概率为pi,且每张重复的卡可以兑换成硬币,k个硬币可以兑换任意一张卡(硬币会攒起来在恰好可以兑换所有n张卡的时候一次性兑换)。问小明得到n张卡的期望抽卡次数是多少。(n<=16)
分析:
氪佬小明一看到n<=16就知道要状压dp,思路就是存储每一个状态的概率,答案就是对于所有满足条件的状态 求和{状态概率*状态抽牌数}。
故设dp[i][j]为:从初始状态到手牌为i,持有j个硬币的概率,状态转移方程可以表达为dp[i][j]=求和_u{ dp[i少一张牌u][j]*p[u] } + dp[i][j-1]*(i中所有手牌的概率总和)
但是需要注意的是,对于状态dp[i][j],如果此时硬币恰好能够兑换所有牌,那么对于i,硬币>j+k的概率均为0(因为此时小明已经兑换完所有的牌,给他的怨种朋友展示去了)。
至于为什么是j+k而不是j,是因为这<k个硬币是前面而非最后积攒到的,小明并不总是先不重复地抽完x张牌再抽取k*(n-x)个硬币来完成任务。
注意输出10位小数否则爆零,哈哈。
#include <cstdio> #include <iostream> #include <algorithm> #include <vector> #include <set> #include <string.h> #define up(l,r,i) for(int i=l;i<=r;i++) #define dn(l,r,i) for(int i=r;i>=l;i--) typedef long long ll; using namespace std; inline int _max(const int& a,const int& b){return a>b?a:b;} inline int _min(const int& a,const int& b){return a<b?a:b;} int n,k; double dp[1<<17][82]; double p[17],rp[17]; double remain(int x){ double ret = 0; int cnt = 0; while(x){ if(x%2 == 1) ret += p[cnt]; cnt++; x>>=1; } return ret; } void p2(int x){ int v[20],i=0; if(x == 0){ cout<<0; return; } while(x){ v[i++] = x%2; x >>=1; } dn(0,i-1,j){ cout<<v[j]; } } int g1n(int x){ int ret = 0; while(x){ ret += x%2; x>>=1; } return ret; } void solve(){ dp[0][0] = 1; up(1,(1<<n)-1,i){ //p2(i); up(0,n-1,j){ if(1<<j > i) break; int v = i&(~(1<<j)); if(v == i) continue; //p2(v); up(0,k*(n-g1n(i)+1)-1,l){ dp[i][l] += dp[v][l]*p[j]; } } up(1,k*(n-g1n(i)),l){ dp[i][l] += dp[i][l-1]*remain(i); } } } int main() { //freopen("y.in","r",stdin); //ios::sync_with_stdio(false); cin>>n>>k; up(0,n-1,i) {cin>>p[i];rp[i] = 1/p[i];} solve(); double ans = 0; up(1,(1<<n)-1,i){ int n1 = g1n(i); int coin = k*(n-n1); int card = coin + n1; ans += card * dp[i][coin]; up(1,k-1,j){ ans += (card+j)*dp[i][coin+j]; } } printf("%.10f",ans); return 0; }