cf261 B. Maxim and Restaurant(01背包+排列组合)
题意:
n个客人,每个客人有体积 \(a_i\)。客人随机排列成一排,依次进入容积为p的餐厅,直到某个客人进不了了就停止(就算后面有个体积超小的也没法进)。求能进去的客人数的期望。
\(1\le n,p,a_i \le 50\)
思路:
先特判一下所有人都能进。
\(f(i,j,k)\) 表示在前 \(i\) 个人中选 \(j\) 个(不计顺序),总体积为 \(k\) 的方案数。
\(f(i,j,k)+=f(i-1,j-1, k-a_i)\) ,可以优化掉一维变成 \(f(j,k)\),记得倒序更新。
设进来了 \(j\) 个人后,第 \(i+1\) 个人恰好进不来,则有 \(k\le p,a_{i+1}> p-k \implies k \in [\max\{0,p-a_{i+1}+1\},p]\)
枚举第 \(i+1\) 个人,
\[\frac {\sum\limits_j\sum\limits_k j \cdot j!(n-j-1)!f(n,j,k)} {n!} \]注意这里 \(f(j,k)\) 表示在n个人中选 \(j\) 个,但不能选 \(a_{i+1}\),总体积恰为 \(k\) 的方案数。为此,每次都重新求dp
const int N = 53;
int n, a[N], p;
db jie[N], f[N][N], ans;
signed main()
{
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
cin >> p;
if(accumulate(a + 1, a + 1 + n, 0) <= p)
return cout << n, 0; //特判
jie[0] = 1; //预处理阶乘
for(int i = 1; i <= n; i++) jie[i] = i * jie[i-1];
for(int x = 1; x <= n; x++) //枚举进不来的那个人
{
//dp,01背包求方案数
memset(f, 0, sizeof f);
f[0][0] = 1;
for(int i = 1; i <= n; i++) if(i != x) //忽略a[x]
for(int j = i; j >= 1; j--)
for(int k = p; k >= a[i]; k--)
f[j][k] += f[j-1][k-a[i]];
//统计答案
for(int j = 1; j <= n; j++) //进来的人数
for(int k = max(0, p-a[x]+1); k <= p; k++) //进来的人要填充的体积
ans += j * jie[j] * jie[n-j-1] * f[j][k];
}
cout << ans / jie[n];
}