題解-CTS2019隨機立方體

problem

\(\mathtt {loj-3119}\)spa

題意概要:一個 \(n\times m\times l\) 的立方體,立方體中每一個格子上都有一個數,若是某個格子上的數比三維座標中至少有一維相同的其餘格子上的數都要大的話,咱們就稱它是極大的。將 \(n\times m\times l\) 的排列隨機填入這些格子,求恰有 \(k\) 個極大的數的機率。\(T\) 組數據。code

\(T\le 10,\ 1\le n,m,l\le 5\times 10^6,\ 1\le k \le 100\),時限 \(5s\)遞歸

Solution

爲啥CTS比APIO難這多?我果真仍是不會數數吶get

根據這一個月來的數數經驗……發現:io

  • 求答案爲 \(k\) (權值)的題:應該是轉換成 「答案小於等於 \(k\)」 減去 「答案小於等於 \(k-1\)」。
  • 求剛好 \(k\) 個(數量)的題:應該是轉換成 「至少有 \(k\) 個」,再利用二項式反演獲得答案。

因此這裏設 \(f[i]\) 表示至少有 \(i\) 個極大值的機率,答案即爲:
\[ \sum_{i=k}^{\min\{n,m,l\}}(-1)^{i-k}\binom ik f[i] \]
問題轉換成求 \(f[i]\)class

首先選出這 \(i\) 個肯定的極大值的座標,係數爲:\(P_n^iP_m^iP_l^i\)(考慮順序,後邊要用到)方法

定義一個座標 \((x_0,y_0,z_0)\) 的控制範圍爲 三個平面 \(x=x_0,y=y_0,z=z_0\) 的並(因此極大值的定義即爲:該點權值 爲 其控制範圍上點的權值最大值)im

問題轉化爲求這 \(i\) 個座標的控制範圍的並內,有多少種安排數字順序(不是權值而是大小關係,由於目前已經選出了這些座標,而且如今要求的是機率,而非方案數)的方法,使得對於每一個選定的座標,其都爲本身控制範圍內的最大值。經驗

直接考慮這個問題很差考慮,須要找到突破口,而這裏的突破口就是 「這些控制範圍的並內,最大值必定是一個極大值」。因爲咱們在選出這 \(i\) 個座標的時候,已經考慮了順序問題,因此若 \(i\)個極大值控制範圍的並 大小爲 \(S_i\),則這件事情發生的機率爲 \(\frac 1{S_i}\),而且發生這件事情後,全部被這個極大值控制的點都沒用了,則將問題轉化爲選出 \(i-1\) 個座標的狀況,如此遞歸,計算獲得的權值爲 \(\prod_{j=1}^i\frac 1{S_j}\)數據

至於如何計算 \(S_i\),能夠發現取全局減多餘可得 \(S_i=nml-(n-i)(m-i)(l-i)\),或是直接考慮容斥 \(S_i=(nm+nl+ml)i-(n+m+l)i^2+i^3\)

彙總一下,獲得:
\[ f[i]=P_n^iP_m^iP_l^i\prod_{j=1}^i\frac 1{S_j}\\ =P_n^iP_m^iP_l^i\prod_{j=1}^i\frac 1{nml-(n-i)(m-i)(l-i)}\\ Ans=\sum_{i=k}^{\min\{n,m,l\}}(-1)^{i-k}\binom ikf[i]\\ =\sum_{i=k}^{\min\{n,m,l\}}(-1)^{i-k}\binom ikP_n^iP_m^iP_l^i\prod_{j=1}^i\frac 1{nml-(n-i)(m-i)(l-i)} \]
這個式子是線性的,但因爲後頭那個 \(\prod\) 須要求逆元,因此複雜度爲 \(O(\min\{n,m,l\}\log p)\),如此能過 \(80\)

考慮到這個東西其實是要求每個前綴積的逆元,和求階乘逆元相似,能夠先求出整個前綴積的逆元,再從後面往前乘,複雜度 \(O(\min\{n,m,l\}+\log p)\)

設:
\[ a_i=nml-(n-i)(m-i)(l-i)\\ b_i=\prod_{j=1}^ia_j\\ c_i=\frac 1{b_i} \]
能夠 \(O(1)\) 獲得 \(a_i\)\(O(n)\) 獲得 \(b_i\)\(O(\log p)\) 獲得 \(c_n=\frac 1{b_n}\)\(O(n)\) 獲得 \(c_i=c_{i+1}\cdot a_{i+1}\)

Code

//loj-3119
#include <cstdio>
typedef long long ll;

const int N = 5001010, p = 998244353;
int fac[N], ifac[N];
int coe[N], h[N], ih[N];
int n, m, l, k;

inline int qpow(int A, int B) {
    int res = 1; while(B) {
        if(B&1) res = (ll)res * A%p;
        A = (ll)A * A%p, B >>= 1;
    } return res;
}

int main() {
    fac[0] = 1;
    for(int i=1;i<N;++i) fac[i] = (ll)fac[i-1] * i%p;
    ifac[N-1] = qpow(fac[N-1], p-2);
    for(int i=N-1;i;--i) ifac[i-1] = (ll)ifac[i] * i%p;
    
    int T; scanf("%d",&T);
    while(T--) {
        scanf("%d%d%d%d", &n, &m, &l, &k);
        if(n > m) n ^= m, m ^= n, n ^= m;
        if(n > l) n ^= l, l ^= n, n ^= l;
        
        const int s2 = ((ll)n*m + (ll)m*l + (ll)n*l)%p, s1 = n+m+l;
        for(int i=h[0]=1;i<=n;++i) {
            coe[i] = (s2 - (ll)s1 * i + (ll)i*i + (ll)p*p)%p * i%p;
            h[i] = (ll)h[i-1] * coe[i]%p;
        }
        ih[n] = qpow(h[n], p-2);
        for(int i=n;i;--i) ih[i-1] = (ll)ih[i] * coe[i]%p;
        
        const int nml_k = (ll)fac[n] * fac[m]%p * fac[l]%p * ifac[k]%p;
        
        int Ans = 0;
        for(int i=k;i<=n;++i) {
            int vl = (ll)nml_k * fac[i]%p * ifac[i-k]%p * ifac[n-i]%p * ifac[m-i]%p * ifac[l-i]%p;
            vl = (ll)vl * ih[i]%p;
            if(i-k&1) vl = p - vl;
            (Ans += vl) >= p && (Ans -= p);
        }
        printf("%d\n", Ans);
    }
    return 0;
}
相關文章
相關標籤/搜索