G - BBQ Hard
AGC001E
AT1983
首先我是完全不会,所以学习了别人的小想法。
朴素的算法是\(O(n^2)\)的,因此一定会炸,所以我们要将\(i\)和\(j\)分离,以求得一个\(O(n)\)或与\(n\)无关的算法
但是万恶的组合数让我们毫无头绪,这时,你会发现,你迷失在了数字的海洋里
为了改变现状,你决定,成为偶像 考虑组合数的组合意义
\(C_{x + y}^{x}\)可以表示为从点\((0,0)\)到\((x,y)\)的走法数量
因此\(C_{a_i+b_i+a_j+b_j}^{a_i+a_j}\)表示为从点\((0,0)\)到\((a_i+a_j,b_i+b_j)\)的走法数量
把原点平移一下,\(C_{a_i+b_i+a_j+b_j}^{a_i+a_j}\)表示为从点\((-a_i,-b_i)\)到\((a_j,b_j)\)的走法数量
考虑把所有的\((-a_i,-b_i)\)标记\(dp\)值+1,
然后\(dp[i][j] = dp[i - 1][j] + dp[i][j - 1]\)递推
统计所有\((a_i,b_i)\)的\(dp\)值的和
但是这样会重复,对于原题的式子\(\sum_{i = 1}^{n} \sum_{j = i + 1}^{n} C_{a_i+a_j+b_i+b_j}^{a_i+a_j}\),我们算了\(i == j\) 时的情况,同时每对\((i,j)\)还算了两遍
减去即可
#include<bits/stdc++.h>
using namespace std;
const int N = 2050;
const int mod = 1e9 + 7;
int n;
int a[200050],b[200050];
int dp[N << 1][N << 1];
long long fac[10050];
long long inv[10050];
long long ksm(long long x,int y){
long long z = 1;
while(y){
if(y & 1) z = z * x % mod;
y >>= 1;
x = x * x % mod;
}
return z;
}
long long C(int n,int m){
if(n < m || m < 0) return 0;
if(n == m || m == 0) return 1;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int main(){
fac[0] = 1;
for(int i = 1; i <= 10000; ++ i) fac[i] = fac[i - 1] * i % mod;
inv[10000] = ksm(fac[10000],mod - 2);
for(int i = 9999; i >= 0; -- i) inv[i] = inv[i + 1] * (i + 1) % mod;
scanf("%d",&n);
for(int i = 1; i <= n; ++ i)
scanf("%d%d",&a[i],&b[i]);
for(int i = 1; i <= n; ++ i)
dp[2001 - a[i]][2001 - b[i]] += 1;
for(int i = 1; i <= 4005; ++ i)
for(int j = 1; j <= 4005; ++ j){
dp[i][j] += (dp[i - 1][j] + dp[i][j - 1]) % mod;
dp[i][j] %= mod;
}
long long ans = 0;
/*for(int i = 1998; i <= 2003; ++ i) {
for(int j = 1999; j <= 2002; ++ j)
printf("%d ",dp[i][j]);
puts("");
}*/
for(int i = 1; i <= n; ++ i){
//printf("%d\n",dp[2001 + a[i]][2001 + b[i]]);
ans = ans + dp[2001 + a[i]][2001 + b[i]], ans %= mod;
}
//printf("%lld\n",ans);
for(int i = 1; i <= n; ++ i){
ans -= C(a[i] + a[i] + b[i] + b[i], a[i] + a[i]);
ans = (ans + mod) % mod;
}
ans = ans * ksm(2, mod - 2) % mod;
printf("%lld\n",ans);
return 0;
}