题解 P5400 【[CTS2019]随机立方体】
command_block
2019-07-17 14:16:28
题目Link:[P5400 [CTS2019]随机立方体](https://www.luogu.org/problemnew/show/P5400)
MC大法好!**MC造图**解此题!
有一个 $n\times m\times l$ 的立方体,立方体中每个格子上都有一个数,如果某个格子上的数比三维坐标**至少有一维相同**的其他格子上的数都要大的话,我们就称它是极大的。
将 $1\sim n\times m\times l$ 这 $n\times m\times l$ 个数等概率随机填入 $n\times m\times l$ 个格子(即任意数字出现在任意格子上的概率均相等),使得每个数恰出现一次,求**恰有** $k$ 个极大的数的概率。答案对 $998244353$(一个质数)取模。
多组数据。
$1\le n,m,l\le 5000000$,$1\le k\le 100$,$1\le T\le 10$
设$G(k)$是求**恰有** $k$ 个极大的数的**方案数**。
首先这个**恰好**非常难受,我们按照套路采用**重复统计**,即:首先弄出$k$个极大值,然后放任自流,这样方案数称为$F(k)$。
容易得到:没有两个级大数会在同一个平面上,所以最多有$min(n,m,l)$个级大数,设$R=min(n,m,l)$。
这里要特判一下,如果$k>R$则输出0。
那么就能得到$F(k)=\sum\limits_{i=k}^R\dbinom{i}{k}G(i)$
解释一下为什么:如果一个方案中有$i$个极大值,那么你只要选中其中的$k$个,这个方案就会被统计一次,随意一共被统计了$\dbinom{i}{k}$次。
所以这里要再次声明:所谓的$F(k)$并**不是**至少有$k$个极大的数的方案数,否则的话$F(k)-F(k-1)$就是答案了。
(这个错误好像大多数题解都有犯过,我初学的时候一直认为$ANS=F(k)-F(k-1)$,WA了一版之后才意识到错误)
[$\text{二项式反演}$](https://www.luogu.org/blog/command-block/yi-suo-ji-guai-di-fan-yan)可得$G(k)=\sum\limits_{i=k}^R(-1)^{i-k}\dbinom{i}{k}F(i)$
只用求$G(k)$,所以上述式子我们直接暴力算复杂度正确。
问题在于如何求出$F(k...R)$。
为了方便,这里设$T=nml$.
$F(k)=$选出$k$个级大数位置的方案数$*$被级大数影响的数的填写方法$*$不被影响的数的填写方法。
- 选出$k$个级大数的方案数
没有两个级大数会在同一个平面上,所以这个东西并不是简单地等于$T^{\underline{k}}$
第一个级大数放置之后,有三个平面的数必须小于该数,其他的位置并没有影响。
所以可选的位置变成了$(n-1)(m-1)(l-1)$个,我们发现就是把原来的空间从各个方向分别削去了1,变成了子问题。
那么总的方案数是$\frac{1}{k!}\prod\limits_{i=0}^{k-1}(n-i)(m-i)(l-i)$,因为这些极大值之间是无序的。
- 被级大数影响的数的填写方法
本题最麻烦的东西。
这$k$个极大值影响的的数的个数是可以求的,即为$T-(n-k)(m-k)(l-k)$
设$G_i=T-(n-i)(m-i)(l-i)$.
我们先要选出$G_k$个数值,这部分方案数是$\dbinom{T}{G_k}$.
此外,我们还要考虑这$G_k$个数怎么摆放。
第一个级大数放置之后,有三个平面的数必须小于该数,其他的位置并没有影响。
第二个级大数放置之后,有三个平面的数必须小于该数,其他的位置并没有影响。
问题在于这六个平面**有交**,那样子是无法分析的。
不过我们发现,假如第一个数小于第二个数的话,那么第一个数控制的三个平面必然都小于第二个数,也就是说,**没有影响**。
容易发现,对于一种合法方案,我们可以任意交换两个面,交换后仍然合法。
那么我们前面已经钦定了$k$个极大值的位置,那么我们把它们一顿swap,得到下图:
由于我们在这里钦定了极大值的相对大小,所以这里还要乘上$(k!)$
![](https://cdn.luogu.com.cn/upload/pic/64260.png)
![](https://cdn.luogu.com.cn/upload/pic/64263.png)
彩色羊毛表示极大值,玻璃表示这些极大值的控制范围。
那么如图,对应颜色玻璃玻璃就是该颜色羊毛的控制区域。
设$\text{黄}<\text{蓝}<\text{红}<\text{绿}$,反正是可以随便交换的。
那么,对于蓝色的区域,它们**只需要**满足小于蓝色羊毛就好了。
我们可以容易地算出每个区域的大小,比如蓝色区域的大小是$(n-1)(m-1)(l-1)-(n-2)(m-2)(l-2)$
为了方便,我们设$g_i=(n-i)(m-i)(l-i)$.
那么第$i$层(从外到里)的大小为$g_{i-1}-g_i$
我们把这个东西**从里到外**一层一层剥开来理解:
显而易见的,值$nml$一定是极大值,所以$\text{绿}=nml$
这里$k=4$。
我们知道$|\text{绿}|=g_3-g_4$,数值上有全部的$G_4$个可随意排列。
那么方案总数是$A_{G_4-1}^{g_3-g_4-1}=\dfrac{(G_4-1)!}{(G_4-1-(g_3-g_4-1))!}=\dfrac{(G_4-1)!}{G_3!}$.
(z这里之所以减1是因为我们钦定了一个数作为极大值)
对于下一层,可以看做变成了$k$小了1(从里面被剥掉了一层)的子问题。
所以总方案数是$\prod\limits_{i=1}^k\dfrac{(G_i-1)!}{G_{i-1}!}$
这部分的总方案数乘起来就是$k!\dbinom{N}{G_k}\prod\limits_{i=1}^k\dfrac{(G_i-1)!}{G_{i-1}!}$
- 其他的数的填法
放任自流就好了,剩下$g_k$个数,那么直接$g_k!$即可。
我们把公式汇总一下:
$F(k)=\frac{1}{k!}\prod\limits_{i=0}^{k-1}g_ig_k!k!\dbinom{N}{G_k}\prod\limits_{i=1}^k\dfrac{(G_i-1)!}{G_{i-1}!}$
$=\dfrac{N!}{g_k!G_k!}\prod\limits_{i=0}^{k-1}g_ig_k!\prod\limits_{i=1}^k\dfrac{(G_i-1)!}{G_{i-1}!}$
因为我们这里算的是方案数,这里除以$N!$就是概率。
$=\dfrac{1}{G_k!}\prod\limits_{i=0}^{k-1}g_i\prod\limits_{i=1}^k\dfrac{(G_i-1)!}{G_{i-1}!}$
那么到了这里,你会注意到$G_k$是$O(nk)$级别的,那么暴力预处理所有的$G!$就能得到80分。
我们发现式子的前面有一个$\dfrac{1}{G_k!}$,把它塞进后面的$\prod$里面。
$=\prod\limits_{i=0}^{k-1}g_iG_0!\prod\limits_{i=1}^k\dfrac{(G_i-1)!}{G_i!}$
$=\prod\limits_{i=0}^{k-1}g_iG_0!\prod\limits_{i=1}^k\dfrac{1}{G_i}$
由于$G_0!=0!=1$得到$F(k)=\prod\limits_{i=0}^{k-1}g_i\prod\limits_{i=1}^k\dfrac{1}{G_i}$
我们发现这个东西十分的简洁,不过我们对于$G$要采用线性求逆元才行,否则复杂度多一个log。
求出$F$之后暴力二项式反演即可。
总复杂度$O((n+k)T)$(注意卡常数)
由于这题时间限制很松,我就$O((n+k)Tlog(mod))$艹过去了……
```cpp
#include<algorithm>
#include<cstdio>
#define mod 998244353
#define MaxN 5000500
using namespace std;
long long powM(long long a,long long t=mod-2)
{
a=(a%mod+mod)%mod;
long long ans=1;
while(t){
if(t&1)ans=(ans*a)%mod;
a=(a*a)%mod;
t>>=1;
}return ans;
}
long long fac[MaxN],inv[MaxN];
void Init()
{
fac[0]=fac[1]=inv[0]=inv[1]=1;
for (int i=2;i<=5000000;i++){
fac[i]=fac[i-1]*i%mod;
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
}for (int i=2;i<=5000000;i++)
inv[i]=inv[i]*inv[i-1]%mod;
}
long long C(int n,int m)
{return fac[n]*inv[n-m]%mod*inv[m]%mod;}
int F[MaxN],G[MaxN],Gl[MaxN],R,n,m,l,k,T;
inline int g(int k)
{return 1ll*(n-k)*(m-k)%mod*(l-k)%mod;}
int main()
{
Init();Gl[0]=1;
int qt;
scanf("%d",&qt);
while(qt--){
scanf("%d%d%d%d",&n,&m,&l,&k);
R=min(n,min(m,l));
if (k>R){printf("0\n");continue;}
T=1ll*n*m%mod*l%mod;
for (int i=1;i<=R;i++)
G[i]=powM((T-g(i)+mod)%mod);
long long buf=1;
for (int i=1;i<=R;i++)
F[i]=buf=buf*g(i-1)%mod*G[i]%mod;
buf=0;
for (int i=k;i<=R;i++)
if ((i-k)&1)
buf=(buf-C(i,k)*F[i])%mod;
else buf=(buf+C(i,k)*F[i])%mod;
printf("%lld\n",(buf+mod)%mod);
}return 0;
}
```