球(ball)
【問題描述】
小 T 有 n 個桶和 2n − 1 個球,其中第 i 個桶能裝前 2i − 1 個球。每一個桶只能裝一個球。
如今小 T 取了 m 個桶和 m 個球,並將這些球各自放在這些桶裏。問這樣的方案有多少。
兩種方案不一樣當且僅當選擇了不一樣的桶或球或者同一個桶在兩種方案放了不一樣的球。
因爲方案的數量可能很大,因此只須要求方案數模 998244353 後的結果。
【輸入格式】
從輸入文件 ball.in 中讀入數據。
第一行一個整數 T,表示數據組數。
接下來 T 行,每行兩個整數 n, m,含義見【問題描述】。
【輸出格式】
輸出到文件 ball.out 中。
輸出共 T 行,每行一個整數表示一組數據的答案。
【樣例 1 輸入】
4
1 1
2 1
2 2
3 2
【樣例 1 輸出】
1
4
2
18
【樣例 1 說明】
對於 n = m = 1 的狀況,只有選擇第一個球和第一個桶,並將第一個球放在第一個桶裏這一種方案。
對於 n = 2, m = 2 的狀況,會選擇全部桶,第一個桶裏放的必定是第一個球,因而第二個桶裏能夠放第二個或第三個球,共兩種方案。
【樣例 2 輸入】
4
1000 1
10000 1
100000 1
1000000 1
【樣例 2 輸出】
1000000
100000000
17556470
757402647
【子任務】
保證 1 ≤ T ≤ 1E5, 1 ≤ m ≤ n ≤ 1E7。ios
首先看到\(10^7\)的數據和僅有2個參數的較多詢問,立刻想到這是一道和預處理階乘有關的題。spa
而後看題目,是一道計數題,結合前面的想法,預估是一道數學題,且極可能是結論題code
而後就分析一下,桶能夠選擇的球的區間存在包含關係,前面的桶選擇一個球放入後,後面的桶可選擇的球就會減1,用式子表達即
\[ Ans= \sum_{i_1 = 1}^n \sum_{i_2 = i_1+1}^n \sum_{i_3 = i_2+1}^n ... \sum_{i_m = i_{m-1}+1}^n (2 i_1 - 1)(2 i_2 - 2) ... (2i_m - m) \]
對這種變量不重複的枚舉方式,有一個經常使用的化法,就是反過來枚舉,使每一個變量的下界爲\(1\),方便後續化簡
\[ \sum_{i_m = m}^{n} \sum_{i_{m-1} = m-1}^{i_m -1} \sum_{i_{m-2} = m-2}^{i_{m-1} -1} ... \sum_{i_{2} = 2}^{i_{3} -1} \sum_{i_{1} = 1}^{i_{2} -1} (2 i_1 - 1)(2 i_2 - 2) ... (2i_m - m) \]
直接把每一項提出去得
\[ \sum_{i_m = m}^{n} (2i_m - m) \sum_{i_{m-1} = m-1}^{i_m -1} (2i_{m-1} - (m-1)) ... \sum_{i_{2} = 2}^{i_{3} -1} (2 i_2 - 2) \sum_{i_{1} = 1}^{i_{2} -1} (2 i_1 - 1) \]
emm而後發現,這樣的式子,從末尾開始,每個求和都只與前一個求和給的變量有關,若是咱們對最後一個求和預處理一下,而後用它來處理倒數第2個求和,而後用這來出來倒數第3個求和......處理到最後就是答案了!blog
實現就一個二維的dp
\[ f(0,j) = 1 \\ f(i,i-1) = 0 \\ f(i,j) = f(i,j-1) + (2j-i)f(i-1,j-1) \]
那麼,最終答案就是\(f(m,n)\)數學
這樣就能夠得到70分string
觀察推出來的式子,是否是挺像組合數的遞推式?it
考慮打表出\(f\)矩陣找規律io
發現\(f(i,i) = i!\),對每行都除掉他class
發現每個數字都是徹底平方數,開方後就成爲一個近似楊輝三角的東西stream
因而愉快的發現了規律。
\[ Ans = m! (C_n^m)^2 \]
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<ctime> #include<cstdlib> #include<algorithm> #include<queue> #include<vector> #include<map> using namespace std; typedef long long ll; const ll MOD=998244353; ll QPow(ll x,ll up){ x%=MOD; ll ans=1; while(up) if(up%2==0) x=x*x%MOD,up/=2; else ans=ans*x%MOD,up--; return ans; } ll Inv(ll x){return QPow(x,MOD-2);} const ll MXN=1E7+5; ll fac[MXN]; ll facInv[MXN]; void SpawnFac(ll sz){ fac[0]=1;for(ll i=1;i<=sz;i++) fac[i]=fac[i-1]*i%MOD; facInv[sz]=Inv(fac[sz]); for(ll i=sz-1;i>=1;i--) facInv[i]=facInv[i+1]*(i+1)%MOD; facInv[0]=1; } ll C(ll n,ll m){ if(n<m) return 0; return fac[n]*facInv[m]%MOD*facInv[n-m]%MOD; } int main(){ //freopen("ball.in","r",stdin); //freopen("ball.out","w",stdout); SpawnFac(1E7+1); ll T;scanf("%lld",&T);while(T--){ ll n,m;scanf("%lld%lld",&n,&m); ll ans=fac[m]*C(n,m)%MOD*C(n,m)%MOD; printf("%lld\n",ans); } return 0; }
這個結論題仍是蠻有意思的,推導套路值得思考
關於該結論的證實:
反正我是沒有看懂(