這遍題解需要用到逆元,學習這個新知識用了兩三個小時。
非常有意思的一道題,可以幫助自己拓展思維。
首先,我們可以想像組合數的幾何意義。
組合數
從n + m 中選 n 個的組合數就是從(0,0)走到(n,m)座標的方案數。
題目所給的 n 是 ai + bi,m 是 aj + bj。
所以暴力的做法是,首先求得每一個點的組合數,然後求和。
但是對於每一個點的組合數他顯然是要(n)^2 的處理時間,因為 n m 都是和i j 有關的,顯然是過不了的。
那麼我現在考慮把上面的矩形平移到 n 和m 只和 j 有關係。
那為什麼正確性不變呢?
因為矩形的大小沒有變化,從(-ai,-bi)走到(aj,bj)和原本(0,0)出發是一樣的。
那我們是不是可以透過O( (max(a) + max( b) )^2 )的時間得出每個點的組合數?顯然a和b最大是2000,所以可以接受的。利用二維dp數組,然後好像過河卒一樣求出每個點的方案數,這裡要注意的事項就是,我們的下標的位置不可以是負數,所以我們拿a和b的最大數值作為中心點,然後把他當作(0,0)進行操作。就像下面一樣
最後我們透過循環題目要求的點的和就行了。
但是如果你沒有利用平移這個技巧,你會需要循環 i 和 j 來獲得題目要求的點,那這樣的話又要花掉 n^2 的時間。如果有了平移技巧,i 和 j 都會變成一樣,我們可以直接枚舉 i 就行了,那就下降成 n 了。
dp formula:
但是由於題目表明 i = j 的情況都不能算是組合數,所以我們要把每一個點減走重複數量,就是
Time complexity: O( (max(a) + max( b) )^2 + n)
代碼
求出逆元的代碼,組合數上涉及除法取模。
const int Mod = 1e9 + 7;
int quickPow(int x, int n){
long long res = 1;
while(n){
if(n&1) (res *= n)%=Mod;
(n *= n)%=Mod;
n>>=1;
}
return res;
}
int findInv(int a){
return quickPow(a, Mod-2);
}
組合數求法
我們預處理出逆元和階乘的數值,之後就可以O(1)求出。
主代碼
#include<iostream>
#include<cstring>
using namespace std;
long long fac[16000], inv[16000], S = 2050, ans = 0, MOD = 1e9 + 7;
long long dp[4500][4500];
long long a[200005], b[200005];
long long quickPow(int x, int n){
long long res = 1;
while(n){
if(n&1)(res =res % MOD * x % MOD)%=MOD;
(x = x % MOD * x % MOD)%=MOD;
n>>=1;
}
return res;
}
int findInv(int a){
return quickPow(a, MOD-2)%MOD;
}
int combin(int n, int r){
return fac[n] * inv[n - r] % MOD *inv[r] % MOD;
}
void work(){
int n;
cin >> n;
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++){
cin >> a[i] >> b[i];
dp[S - a[i]][S - b[i]]++;
}
inv[0] = findInv(fac[0]);
fac[1] = 1;
fac[0] = 1;
for(long long i = 1; i <= 8000; i++){
fac[i] = fac[i - 1] * i % MOD;
inv[i] = findInv(fac[i]);
}
for(int i = 1; i <= S*2; i++){
for(int j = 1; j <= S*2; j++){
(dp[i][j] = dp[i][j] % MOD + dp[i - 1][j] % MOD + dp[i][j - 1] % MOD)%=MOD;
}
}
for(int i = 1; i <= n; i++){
(ans = ans % MOD + dp[S + a[i]][S + b[i]] % MOD) %= MOD;
(ans -= combin(2*a[i] + 2*b[i], 2*a[i])) %= MOD;
(ans += MOD) %= MOD;
}
cout << (ans * inv[2]) % MOD;
}
int main(){
work();
return 0;
}