Problem
計算:
\[\sum_{i=1}^{n}\sum_{j=i+1}^n\binom{a_i+b_i+a_j+b_j}{a_i+a_j}
\]
其中\(n \leq 2 \times 10^5\),\(a_i,b_i \leq 2000\)
Solution
以\(a_i\)代\(a_i+b_i\)則等價於求
\[\sum_{i=1}^{n}\sum_{j=i+1}^n\binom{a_i+a_j}{b_i+b_j}
\]
考慮使得式子變得更加對稱,我們可以不限制\(i,j\)的相對大小,之後減去\(i=j\)的情況再除以\(2\)即可。
\[\sum_{i = 1}^{n}\sum_{j = i + 1}^{n}\binom{a_i+a_j}{b_i+b_j}=
\dfrac{1}{2}\left( \sum_{i = 1}^{n}\sum_{j = 1}^{n}\binom{a_i+a_j}{b_i+b_j}-\sum_{i = 1}^{n}\binom{2a_i}{2b_i}\right)
\]
問題轉化為求
\[\sum_{i=1}^{n}\sum_{j=1}^n\binom{a_i+a_j}{b_i+b_j}
\]
代數法
推導過程
考慮將\(i,j\)拆開分別計算貢獻,可以聯想到Vandermonde卷積
於是原式轉換為
\[\begin{aligned}
\sum_{i=1}^{n}\sum_{j=1}^n\binom{a_i+a_j}{b_i+b_j}
&=\sum_{i=1}^{n}\sum_{j=1}^n\sum_k\binom{a_i}{b_i-k}\binom{a_j}{b_j+k} \\
&=\sum_k\sum_{i=1}^{n}\sum_{j=1}^n\binom{a_i}{b_i-k}\binom{a_j}{b_j+k} \\
&=\sum_k\left(\sum_{i=1}^{n}\binom{a_i}{b_i-k}\right)\left(\sum_{j=1}^n\binom{a_j}{b_j+k}\right) \\
&=\sum_{k}F(k)F(-k)
\end{aligned}
\]
其中\(F(k)=\displaystyle\sum_{i=1}^n\binom{t_i}{a_i+k}\)
然後我們容易想到一個\(O(na)\)的做法,那就是直接計算\(F(k)\),注意由於\(k\)的範圍是\([-2000,2000]\),所以我們需要將\(k\)平移至\([0,4000]\),這樣就可以用一個陣列來儲存\(F(k)\)了。
for (int k = mink; k <= maxk; ++k) {
for (int i = 1; i <= n; ++i) {
if (k + b[i] >= 0 && k + b[i] <= a[i]) {
F[k + SHIFT] = (F[k + SHIFT] + C[a[i]][k + b[i]]) % MOD;
}
}
}
最佳化
這個也許卡卡常就能過了,但是我們嘗試去追求更好的時間複雜度
事實上,我們利用生成函式的相關知識,有
\[\begin{aligned}
F(k)&=\sum_{i=1}^n\binom{t_i}{a_i+k}\\
&= \sum_{i=1}^n(1+x)^{a_i}[x^{b_i+k}]\\
&=[x^k]\sum_{i=1}^n(1+x)^{a_i}x^{-b_i}\\
&=[x^k+SHIFT]\sum_{i=1}^n(1+x)^{a_i}x^{-b_i+SHIFT}\\
&=[x^k+SHIFT]\sum_{A}(1+x)^A\sum_{i,a_i=A}x^{-b_i+SHIFT}
\end{aligned}
\]
其中\([x^i]\)表示\(x^i\)項的係數。
則我們只需要求出
\[\sum_{A}(1+x)^A\sum_{i,a_i=A}x^{-b_i+SHIFT}
\]
這個式子可以用秦九韶演演算法來求,複雜度降低至\(O(a^2)\)
for (int i = 1; i <= n; ++i) {
bInA[a[i]].push_back(-b[i] + SHIFT);
}
for (int i = maxa; i >= 0; --i) {
for (int k = 2* SHIFT; k >= 1; --k) {
F[k] = (F[k] + F[k - 1]) % MOD;
}
for (auto bInAi : bInA[i]) {
F[bInAi] = (F[bInAi] + 1) % MOD;
}
}
完整程式碼
#include<iostream>
#include<vector>
using namespace std;
const int MAX_N = 200005, MOD = 998244353, MAX_A = 4000 + 5, SHIFT = 2000;
int ans, n, a[MAX_N], b[MAX_N], maxa, C[MAX_A][MAX_A], mink, maxk;
int fac[MAX_A * 2], inv[MAX_A * 2], invfac[MAX_A * 2];
int F[MAX_A];//from -2000 to 2000, 加上基數2000
int binom(int n, int k) {
if (n < k) {
return 0;
}
return 1ll * fac[n] * invfac[k] % MOD * invfac[n - k] % MOD;
}
vector<int> bInA[MAX_A];
signed main() {
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i] >> b[i];
a[i] += b[i];
maxa = max(maxa, a[i]);
mink = min(mink, -b[i]);
maxk = max(maxk, a[i] - b[i]);
}
for (int i = 0; i <= maxa; ++i) {
C[i][0] = 1;
for (int j = 1; j <= i; ++j) {
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
}
}
/*for (int k = mink; k <= maxk; ++k) {
for (int i = 1; i <= n; ++i) {
if (k + b[i] >= 0 && k + b[i] <= a[i]) {
F[k + SHIFT] = (F[k + SHIFT] + C[a[i]][k + b[i]]) % MOD;
}
}
}*/
for (int i = 1; i <= n; ++i) {
bInA[a[i]].push_back(-b[i] + SHIFT);
}
for (int i = maxa; i >= 0; --i) {
for (int k = 2* SHIFT; k >= 1; --k) {
F[k] = (F[k] + F[k - 1]) % MOD;
}
for (auto bInAi : bInA[i]) {
F[bInAi] = (F[bInAi] + 1) % MOD;
}
}
for (int k = mink; k <= maxk; ++k) {
ans = (ans + 1ll * F[k + SHIFT] * F[-k + SHIFT] % MOD) % MOD;
}
fac[0] = inv[0] = invfac[0] = 1;
fac[1] = inv[1] = invfac[1] = 1;
for (int i = 2; i <= maxa * 2; ++i) {
fac[i] = 1ll * fac[i - 1] * i % MOD;
inv[i] = 1ll * (MOD - MOD / i) * inv[MOD % i] % MOD;
invfac[i] = 1ll * invfac[i - 1] * inv[i] % MOD;
}
for (int i = 1; i <= n; ++i) {
ans = (ans - binom(2 * a[i], 2 * b[i]) + MOD) % MOD;
}
ans = 1ll * ans * inv[2] % MOD;
cout << ans << endl;
return 0;
}
組合意義法
我們可以把這個值看做在網格圖上的一點\((−a[i],−b[i])\)走到\((a[j],b[j])\)的方案數。
而網格圖走的方案數可以直接遞推得到。
那麼我們對於每個點把它的座標取反到第三象限,然後對於整個座標系計算走到每一個格子的總方案。
遞推式與網格路徑完全相同
f[i][j] = (1ll * f[i][j] + f[i - 1][j] + f[i][j - 1]) % MOD;
需要注意的是初始條件
for(int i = 1; i <= n; i++){
f[SHIFT - a[i]][SHIFT - b[i]]++;
}