1. 程式人生 > >計數難題3:LuoguT46780 妹子序列

計數難題3:LuoguT46780 妹子序列

計數難題3:LuoguT46780 妹子序列

標籤(空格分隔): 計數難題題選

題目大意:

丟個網址:戳我(QwQ)
給定\(n\)\(m\)
\(n\)的所有排列中,逆序對個數為\(m\)的排列個數。資料範圍:\(n,m\leq 10^5\)

題解

樸素\(dp\)\(dp_{i,j}\)表示放完\(i\),有\(j\)個逆序對的方案數。
轉移太簡單了:\(dp_{i,j} = \sum_{k=0}^{i-1} dp_{i-1,k}\) ,複雜度\(O(n^2)\)美滋滋。
注意到放\(i\)時,我們的決策集合為\(k\in [0,i)\)
我們把每次的決策序列寫出來,就一定能對應一個唯一的合法排列。
所以現在問題就變成了:滿足決策的和為\(m\)

的合法決策序列的個數。
構造生成函式:
\[G(x) = \prod_{i=1}^n [\sum_{j=0}^{i-1} x^j]\]

那麼就是要求\(x^m\)項的係數,推式子:

\[G(x) = \prod_{i=1}^n[\sum_{j=0}^{i-1}x^j] = \prod_{i=1}^n \frac{1-x^i}{1-x} = (\frac{1}{1-x})^n \prod_{i=1}^n (1-x^i)\]

顯然\(\frac{1}{1-x} = \sum_{j=0}^{\inf} x^j\) ,考慮\((\frac{1}{1-x})^n\) 的組合意義。
相當於有\(n\)種不同物品可以用,第\(k\)

項的係數即從中選出\(k\)個可重物品的方案數。
即可重組合,所以有:
\[S(x) = (\frac{1}{1-x})^n = (\sum_{j=0}^{\inf}x^j)^n = \sum_{j=0}^{\inf} \binom{j+n-1}{n-1}x^j\]

對於後面的部分:\(T(x) = \prod_{i=1}^n(1-x^i)\) ,同樣考慮組合意義:
\(n\)個物品,第\(i\)個物品的體積為\(i\),第\(k\)項係數即用這些物品填滿大小為\(k\)的揹包的方案數。
古人云:揹包計數 等價於 上升序列計數
這裡每個物品只有一個,所以應該是一個嚴格上升序列
即我們要求:所有元素和為\(k\)

,且嚴格上升的合法序列個數,
\(f_{i,j}\) 表示放了\(i\)個物品,體積和為\(j\)的方案數,轉移做有符號的序列計數。

  • 新增一個物品:\(f_{i-1,j-i}\) (即先給原來的\(i-1\)個物品+1,然後再新增一個1)
  • 把原來所有的物品加\(1\)\(f_{i,j-i}\)
  • 由於物品體積不能超過\(n\),所以強制後減去不合法:\(-f_{i-1,j-(n+1)}\)

綜上所述,轉移方程是(注意轉移時帶符號):\(f_{i,j} = f_{i,j-i} - f_{i-1,j-i} - (-f_{i-1,j-(n+1)})\)
而由於每種體積的物品只有一個,放置物品最多的方案應該是 \(1+2+...+MAX\leq m\)
所以可以放置的物品數是\(2\sqrt m+2\) 級別的,故只用做這麼多次轉移即可。
顯然\(T(x) = \sum_{j=0}^{\inf}[\sum_{i=0}^{2\sqrt m + 2} f_{i,j}]x^j\) ,而我們有:
\[G(x) = S(x) * T(x)\]

所以卷積後即可得到第\(m\)項的係數,複雜度\(O(n\sqrt n)\)

實現程式碼

#include<bits/stdc++.h>
#define _ 100005
using namespace std ;

const int mod = 998244353 ; 
int dp[520][_],f[_],g[_],Fac[_<<1],IFac[_<<1],inv[_<<2],n,m,Ans ; 

int Comb(int N , int M) {
    if(M > N) return 0 ;
    return 1ll * Fac[N] * IFac[M] % mod * IFac[N - M] % mod ; 
}
int main() {
    freopen("testdata.in","r",stdin) ;
    cin >> n >> m ; 
    inv[0] = inv[1] = Fac[0] = Fac[1] = IFac[0] = IFac[1] = 1 ;
    for(int i = 2; i <= n + m ; i ++) {
        Fac[i] = 1ll * Fac[i-1] * i % mod ;
        inv[i] = 1ll * (mod-mod/i) * inv[mod%i] % mod ;
        IFac[i] = 1ll * IFac[i-1] * inv[i] % mod ; 
    }
    for(int i = 0; i <= m; i ++) f[i] = Comb(i + n - 1 , n - 1) ;
    dp[0][0] = 1 ;  g[0] = 1 ;
    int d = 1 ;
    for(int j = 2; j <= m; j ++) if(1ll * j * (j + 1) / 2 >= m) {d = j ; break ;}
    for(int i = 1; i <= d; i ++) {
        for(int j = 0; j <= m; j ++) {
            if(j>=i) dp[i][j] = (dp[i][j - i] - dp[i-1][j - i] + mod) % mod ;
            if(j>=(n+1))
                dp[i][j] = (dp[i][j] + dp[i - 1][j - (n + 1)]) % mod ; 
        }
        for(int j = 0; j <= m; j ++) g[j] = (g[j] + dp[i][j]) % mod ; 
    }
    Ans = 0 ;
    for(int i = 0; i <= m; i ++)
        Ans = (Ans + 1ll * f[i] * g[m - i] % mod) % mod ;
    cout << Ans << endl ;
    return 0 ; 
}