题意
现在要给一个n∗m∗ln*m*ln∗m∗l的矩形的每一位等概率地填上111到n∗m∗ln*m*ln∗m∗l中的一个数,且满足每个数恰好出现一次。定义一个位置为极大值当且仅当所有坐标至少有一维和他相同的位置的数都比他小。问恰好有kkk个极大值的概率。
n,m,l≤5000000,k≤100n,m,l\le5000000,k\le100n,m,l≤5000000,k≤100
分析
考虑容斥,那么就要对每个k≤i≤min(n,m,l)k\le i\le min(n,m,l)k≤i≤min(n,m,l)算出至少有iii个极大值的答案。
假设当前我们已经钦定了这iii个极大值的位置,显然他们每一维的坐标互不相同。
现在只用考虑至少有一维坐标和这iii个位置中的某一个相同的那些位置,显然合法的位置有nml−(n−i)(m−i)(l−i)nml-(n-i)(m-i)(l-i)nml−(n−i)(m−i)(l−i)个。
如果我们把这iii个数按照从大到小的顺序填入,那么最大的数必然是这些位置中的最大值,概率为1nml−(n−i)(m−i)(l−i)\frac{1}{nml-(n-i)(m-i)(l-i)}nml−(n−i)(m−i)(l−i)1
填好最大的数后,和其他i−1i-1i−1个格子没有坐标相同的那(n−i+1)(m−i+1)(l−i+1)−(n−i)(m−i)(l−i)(n-i+1)(m-i+1)(l-i+1)-(n-i)(m-i)(l-i)(n−i+1)(m−i+1)(l−i+1)−(n−i)(m−i)(l−i)个格子无论怎么填都必然合法。
同理,第二大的数必然是剩下的nml−(n−i+1)(m−i+1)(l−i+1)nml-(n-i+1)(m-i+1)(l-i+1)nml−(n−i+1)(m−i+1)(l−i+1)个位置中的最大值,概率为1nml−(n−i+1)(m−i+1)(l−i+1)\frac{1}{nml-(n-i+1)(m-i+1)(l-i+1)}nml−(n−i+1)(m−i+1)(l−i+1)1
所以答案就是∑i=kmin(n,m,l)(−1)i−kCikAniAmiAli∏j=1i1nml−(n−j)(m−j)(l−j)\sum_{i=k}^{min(n,m,l)}(-1)^{i-k}C_i^kA_n^iA_m^iA_l^i\prod_{j=1}^i\frac{1}{nml-(n-j)(m-j)(l-j)}i=k∑min(n,m,l)(−1)i−kCikAniAmiAlij=1∏inml−(n−j)(m−j)(l−j)1
时间复杂度O(n)O(n)O(n)。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
typedef long long LL;
const int N=5000005;
const int MOD=998244353;
int n,m,l,k,jc[N],ny[N],pre[N];
int ksm(int x,int y)
{
int ans=1;
while (y)
{
if (y&1) ans=(LL)ans*x%MOD;
x=(LL)x*x%MOD;y>>=1;
}
return ans;
}
int C(int n,int m)
{
return (LL)jc[n]*ny[m]%MOD*ny[n-m]%MOD;
}
int A(int n,int m)
{
return (LL)jc[n]*ny[n-m]%MOD;
}
void solve()
{
jc[0]=jc[1]=ny[0]=ny[1]=1;
for (int i=2;i<=l;i++) jc[i]=(LL)jc[i-1]*i%MOD,ny[i]=(LL)(MOD-MOD/i)*ny[MOD%i]%MOD;
for (int i=2;i<=l;i++) ny[i]=(LL)ny[i-1]*ny[i]%MOD;
pre[n]=1;
for (int i=1;i<=n;i++) pre[n]=(LL)((LL)n*m%MOD*l%MOD+MOD-(LL)(n-i)*(m-i)%MOD*(l-i)%MOD)*pre[n]%MOD;
pre[n]=ksm(pre[n],MOD-2)%MOD;
for (int i=n;i>1;i--) pre[i-1]=(LL)((LL)n*m%MOD*l%MOD+MOD-(LL)(n-i)*(m-i)%MOD*(l-i)%MOD)*pre[i]%MOD;
int ans=0;
for (int i=k;i<=n;i++)
{
int w=(LL)C(i,k)*A(n,i)%MOD*A(m,i)%MOD*A(l,i)%MOD*pre[i]%MOD;
if ((i-k)&1) (ans+=MOD-w)%=MOD;
else (ans+=w)%=MOD;
}
printf("%d\n",ans);
}
int main()
{
int T;scanf("%d",&T);
while (T--)
{
scanf("%d%d%d%d",&n,&m,&l,&k);
if (n>m) std::swap(n,m);
if (n>l) std::swap(n,l);
if (m>l) std::swap(m,l);
solve();
}
return 0;
}