P5400-[CTS2019]随机立方体【二项式反演,计数】
正题
题目链接:https://www.luogu.com.cn/problem/P5400
题目大意
有一个\(n\times m\times l\)的三维网格,要在每个格子处填上一个数,要求填的数中\(1\sim n\times m\times l\)都恰好出现了一次。
一个极大值被定义为这个格子比其他与它至少有一个坐标相同的格子都大,求恰好有\(k\)个极大值的概率。
\(1\leq n,m,l\leq 5\times 10^6,1\leq k\leq 100,1\leq T\leq 10\)
解题思路
恰好我们很难进行计数,所以我们考虑钦定\(k\)个极大值,然后用二项式反演。
假设\(n\leq m\leq l\),那么极大值个数不超过\(n\)个。
考虑怎么求钦定\(k\)个极大值时的方案,首先每个极大值的位置是没有关系的,因为三个坐标都肯定各不相同。
那么我们就默认第\(i\)个最大值的位置是\((i,i,i)\),且第\(i\)个极大值\(a_i<a_{i+1}\)。
这样的话对于每个极大值限制的范围就是一层一层的嵌套。先考虑不在限制范围内的数,选一些填上,那么方案就是\(\binom{nml}{(n-k)(m-k)(l-k)}\),然后还要考虑在外面填的方案那么总方案就是\(A_{nml}^{(n-k)(m-k)(l-k)}\)。
然后考虑填被限制的数字的方案,从外层开始填,那么第一层就是除了\((1,1,1)\)以外随便填了。
为了方便我们记\(G_k=(n-k)(m-k)(l-k)\)那么第一层的方案就是\((G_0-G_{1}-1)!\),同样推出第二层的方案\((G_1-G_2-1)!\),不过要把第二层的数字穿插在第一层中(除了极大值),那么就是\((G_1-G_2-1)!\times C_{G_0-G_2-1}^{G_1-G_2-1}\)。
那么写出总答案就是
拆分开来
我们会发现我们能用前面的\(\frac{1}{G_k!}\)的某些部分去补足后面\(G_0-G_{i}\)和\(G_0-G_{i+1}-1\)的差,但是因为会有重复部分,我们只提出\([G_0-G_i+1,G_0-G_{i+1}]\)这一部分就有
这样我们用\(O(n)\)预处理逆元的方法就可以做到\(O(Tn)\)了。
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=5e6+10,P=998244353;
ll T,n,m,l,k,fac[N],fnv[N];
ll g[N],G[N],f[N],inv[N],ans;
ll power(ll x,ll b){
ll ans=1;
while(b){
if(b&1)ans=ans*x%P;
x=x*x%P;b>>=1;
}
return ans;
}
ll C(ll n,ll m)
{return fac[n]*fnv[m]%P*fnv[n-m]%P;}
signed main()
{
fnv[0]=fnv[1]=fac[0]=1;
for(ll i=2;i<N;i++)fnv[i]=P-fnv[P%i]*(P/i)%P;
for(ll i=1;i<N;i++)fac[i]=fac[i-1]*i%P,fnv[i]=fnv[i-1]*fnv[i]%P;
scanf("%lld",&T);
while(T--){
scanf("%lld%lld%lld%lld",&n,&m,&l,&k);
ll S=n*m%P*l%P;g[0]=S;ans=0;
if(m<n)swap(n,m);
if(l<n)swap(n,l);inv[0]=1;
if(k>n){puts("0");continue;}
for(ll i=1;i<=n;i++){
G[i]=(S-(n-i)*(m-i)%P*(l-i)%P+P)%P;
g[i]=(n-i)*(m-i)%P*(l-i)%P;
inv[i]=inv[i-1]*G[i]%P;
}
inv[n]=power(inv[n],P-2);
for(ll i=n;i>=1;i--)inv[i-1]=inv[i]*G[i]%P;
f[0]=1;
for(ll i=1;i<=n;i++)f[i]=f[i-1]*g[i-1]%P;
for(ll i=0;i<=n;i++)f[i]=f[i]*inv[i]%P;
for(ll i=k;i<=n;i++)(ans+=C(i,k)*f[i]%P*(((i-k)&1)?(P-1):1)%P)%=P;
printf("%lld\n",ans);
}
return 0;
}