题意:m个游客,n座城市(m, n <= 16), 每个人从1走到n, 每次有一定概率停在原地,然后以后就不前进了。一个人到过一个城会得到一定的愉悦度,对于相邻的两座城,会额外产生Cj / Cj - 1 * Hj的愉悦度,Cj是到过j城的人数,Hj是到过j城的人在这里获得的愉悦度之和。求期望的总愉悦度。
解法:首先,城市给人提供的愉悦度可以直接计算出来。设dp[i][j]代表到在i城的人的集合为j的概率,额外提供的愉悦度期望可以在统计dp[i] -> dp[i + 1]的转移时计算出来。考虑dp[i][j] -> dp[i + 1][k]的转移中,必要条件是k是j的子集,然后这个转移发生的概率可以通过j到k的变化计算出来,愉悦度是cnt[k] * sumh[i + 1][k] / cnt[j],sumh[i + 1][k]代表i + 1城中k集合的人的愉悦度之和。因为期望的可加性,这一次转移对答案的贡献就是概率 * 愉悦度。分析愉悦度的式子,发现枚举了i之后,cnt[k] * sumh[i + 1][k]只和k有关,cnt[j]只和j有关。因此统计转移给答案的贡献之前,可以把每个dp[i][j]除以cnt[j]。这个转移是一个类似高维前缀和的过程,顺次枚举把某一维变成0的转移后,两个集合之间的路径是唯一的,因此只需考虑某维从1变成0的转移即可,复杂度为n * n * 2 ^ n.
3 1 0.1 0.2 0.3 10 20 30 3 3 0.5 0.5 0.5 1 1 1 1 1 1 1 1 1 4 4 0.1 0.4 0.2 0.3 7 2 18 10 2 6 9 5 4 4 19 17 7 3 13 17
60.0000000 6.84375000 34.230645587
#include<iostream> #include<cstring> #include<cstdio> using namespace std; const int N = 18; double f[N][N], dp[N][1<<N], sumh[N][1<<N], h[N][N]; int n,m, num[1<<N]; int C(int x) { int c=0; for(;x;x>>=1) if(x&1) c++; return c; } void work() { for(int i=1;i<=m;i++) { f[i][1]=1; scanf("%lf", &f[i][2]); ///f[i][2]是第i个人去下一个点的概率 for(int j=3;j<=n;j++) f[i][j] = f[i][j-1] * f[i][2]; ///f[i][j] 第i个人到点j概率 } double ans=0; for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) scanf("%lf",&h[i][j]),ans+=h[i][j] * f[i][j]; } int full=1<<m; for(int i=1;i<=n;i++) { for(int j=0;j<full;j++) { dp[i][j]=1; sumh[i][j]=0; for(int k=1;k<=m;k++) if(j&(1<<k-1)) { dp[i][j] *= f[k][i]; sumh[i][j] += h[k][i]; } else dp[i][j] *= 1-f[k][i]; } } for(int i=1;i<n;i++) { for(int k=1;k<full;k++) dp[i][k]/=num[k]; for(int j=1;j<=m;j++) { for(int k=0;k<full;k++) { if(k&(1<<j-1)) { dp[i][k-(1<<j-1)] += dp[i][k] * (1 - f[j][2]); dp[i][k] *= f[j][2]; } } } for(int k=0;k<full;k++) ans+=dp[i][k] * num[k] * sumh[i+1][k]; } printf("%.12f\n", ans); } int main() { num[0]=0; for(int i=1;i<(1<<16);i++) num[i]=C(i); while(cin>>m>>n) work(); return 0; }