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\)
考慮怎麼求欽定\(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_0!}A_{G_0}^{G_k}\prod_{i=0}^{k-1}(G_i-G_{i+1}-1)!C_{G_0-G_{i+1}-1}^{G_i-G_{i+1}-1} \]拆分開來
\[\frac{1}{G_k!}\prod_{i=0}^{k-1}(G_i-G_{i+1}-1)!\frac{(G_0-G_{i+1}-1)!}{(G_i-G_{i+1}-1)!(G_0-G_i)!} \] \[\frac{1}{G_k!}\prod_{i=0}^{k-1}\frac{(G_0-G_{i+1}-1)!}{(G_0-G_i)!} \]我們會發現我們能用前面的\(\frac{1}{G_k!}\)
這樣我們用\(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;
}