CF261B Maxim and Restaurant

· · 题解

什么人能一个人比一张能坐 50 个人的桌子宽?

题意

有个餐馆,有容积 p ,有 n 个人,每个人体积是 a_{i}

n 个人进入的次序完全是随机的,求餐馆里面吃饭人数的期望

p,n,a_{i}\le50

Solve

进入次序就是排列,所以答案最后除个 n!

容易发现一旦餐馆塞满了后面人甭管怎么排都进不去

而一个人能不能排进去也和餐馆里面都塞了些什么人才无关,只用考虑餐馆的容量

考虑枚举进不去餐馆的最后一个人,设 dp[i][j][k] 为前 i 个人进去 j 个人占据 k 个空间的方案数

枚举第一个进不去的人,每次重新把 DP 做一遍(就是不让这个人参与 DP 方程的更新),那么答案总的式子就是

ans=\frac{\sum_{x=1}^{n}\sum_{i=1}^{n-1}\sum_{j=p-a_{x}}^{p}dp[n][i][j]\times j\times i!\times(n-(i+1))!}{n!}

这么做的话总复杂度就是 O(n^3p),但是因为 n,p 很小,所以足以通过此题

但我们还有 O(n^2p) 的做法,先看式子:

ans=\frac{\sum_{i=1}^{n}\sum_{j=0}^{p}dp[i][j]\times i!\times (n-i)!}{n!}

这个式子着实有点迷惑,它计算的实际上是在餐馆里的人,有第一个人,有第二个人到有第 个人中,每个人出现的次数

也就是它每次算的是有多少种情况里面会有第几个人

把式子调整一下表达,可能会更好理解

\sum_{i=1}^{n}(\sum_{j=0}^{p}dp[i][j])\times 1\times i!\times (n-i)! $1$ 代表有正在计算的这一 ‘位子’ 上的人的队列长度,一个人自然长度为 $1

dp[i][j] 表示里面有 i 个人,占有 j 个空间的方案数

可能会觉得很奇怪,感觉这玩意会算重,但并不会,因为你每次统计的贡献不是整个在餐馆里面的人的贡献,而是这个餐馆里面第 i 号人会出现几次

\sum_{j=0}^{p}dp[i][j]$ 表示的就是一共会有几种方案餐馆里面会出现不少于 $i$ 个人,每个人都不一样,所以得乘个 $i!\times (n-i)!

Code

```cpp #include<cstdio> #include<iostream> #include<cstring> typedef long long ll; typedef long double ldouble; typedef unsigned long long ull; const int N=55; ll n,a[N],p,dp[N][N]; double fact[N],sum; int main() { std::ios_base::sync_with_stdio(false); std::cin.tie(0);std::cout.tie(0); std::cin>>n; fact[0]=1; for (int i=1;i<=n;i++) { std::cin>>a[i]; fact[i]=fact[i-1]*i, sum+=a[i]; } std::cin>>p; if (sum<=p) { std::cout<<(double)n; return 0; } dp[0][0]=1; for (int i=1;i<=n;i++) for (int j=i;j>=1;j--) for (int k=p;k>=a[i];k--) dp[j][k]+=dp[j-1][k-a[i]]; double ans=0; for (int i=1;i<=n;i++) for (int j=0;j<=p;j++) ans+=dp[i][j]*fact[i]*fact[n-i]; std::cout<<ans/fact[n]; return 0; } ```