Problem p04051 (C++)

Buggy submission (status: Runtime Error):
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PI pair<int, int>
const int maxm = 2e5 + 5;
const int mod = 1e9 + 7;
// n<=2e5
// a[i],b[i]<=2e3
int fac[maxm], inv[maxm];
int a[maxm], b[maxm];
int d[4005][4005];
const int base = 2025;
int n;
int ppow(int a, int b, int mod) {
  int ans = 1 % mod;
  a %= mod;
  while (b) {
    if (b & 1)
      ans = ans * a % mod;
    a = a * a % mod;
    b >>= 1;
  }
  return ans;
}
void init() {
  fac[0] = 1;
  for (int i = 1; i < maxm; i++)
    fac[i] = fac[i - 1] * i % mod;
  inv[maxm - 1] = ppow(fac[maxm - 1], mod - 2, mod);
  for (int i = maxm - 2; i >= 0; i--)
    inv[i] = (i + 1) * inv[i + 1] % mod;
}
int C(int n, int m) {
  if (m < 0 || m > n)
    return 0;
  return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
signed main() {
  init();
  cin >> n;
  for (int i = 1; i <= n; i++)
    cin >> a[i] >> b[i];
  for (int i = 1; i <= n; i++) {
    d[-a[i] + base][-b[i] + base]++;
  }
  for (int i = 1; i <= 4010; i++) {
    for (int j = 1; j <= 4010; j++) {
      d[i][j] += d[i - 1][j] + d[i][j - 1];
      d[i][j] %= mod;
    }
  }
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    ans += d[a[i] + base][b[i] + base];
    ans %= mod;
  }
  for (int i = 1; i <= n; i++) {
    ans -= C(2 * (a[i] + b[i]), 2 * a[i]);
    ans %= mod;
  }
  ans = (ans * ppow(2, mod - 2, mod) % mod + mod) % mod;
  cout << ans << endl;
  return 0;
}
/*
发现a[i]和b[i]很小,是可以O(n^2)的范围

C(x+y,x)可以看作是(0,0)到(x,y)的方案数,
如果我们要求(0,0)到(x,y)的方案数,
可以令d[i][j]表示(0,0)到(i,j)的方案数,一开始d[0][0]=1.
转移方程为d[i][j]=d[i-1][j]+d[i][j-1].

C(a[i]+a[j]+b[i]+b[j],a[i]+a[j])可以看作:
(0,0)到(a[i]+a[j],b[i]+b[j])的方案数.

枚举i和j是O(n^2)的,显然要优化,
尝试将i和j分开:
将坐标位移一下变为(-a[i],-b[i])到(a[j],b[j])的方案数.
这样i和j就分离了,然后就可以单独计算每个部分的贡献.

观察前面的dp方程,我们一开始令d[0][0]=1,
然后计算出的d[x][y]就是(0,0)到(x,y)的方案数,
那么我们令d[-a[i]][-b[i]]=1,
那么计算出的d[x][y]就是(-a[i],-b[i])到(x,y)的方案数.
这样的话,我们一开始只需要令所有的d[-a[i]][-b[i]]++,
那么计算出来的d[x][y]就是所有数对到达(x,y)的方案数.
因此最后只需要累加d[a[i]][b[i]]即可.
但是存在(-a[i],-b[i])到(a[i],b[i])的方案数,也就是自己和自己匹配,
而题目只需要计算i<j的,因此减掉自己和自己匹配的,方案数为C(2*(a[i]+b[i]),2*a[i]).
最后答案还要再除以2,因为是计算出的是无序的,题目要的是有序的.

参考:
https://www.luogu.com.cn/blog/Ebola-Emperor/solution-at1983
https://www.luogu.com.cn/blog/208529/solution-at1983
*/


Fixed submission:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PI pair<int, int>
const int maxm = 2e5 + 5;
const int mod = 1e9 + 7;
// n<=2e5
// a[i],b[i]<=2e3
int fac[maxm], inv[maxm];
int a[maxm], b[maxm];
int d[4444][4444];
const int base = 2e3 + 5;
int n;
int ppow(int a, int b, int mod) {
  int ans = 1 % mod;
  a %= mod;
  while (b) {
    if (b & 1)
      ans = ans * a % mod;
    a = a * a % mod;
    b >>= 1;
  }
  return ans;
}
void init() {
  fac[0] = 1;
  for (int i = 1; i < maxm; i++)
    fac[i] = fac[i - 1] * i % mod;
  inv[maxm - 1] = ppow(fac[maxm - 1], mod - 2, mod);
  for (int i = maxm - 2; i >= 0; i--)
    inv[i] = (i + 1) * inv[i + 1] % mod;
}
int C(int n, int m) {
  if (m < 0 || m > n)
    return 0;
  return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
signed main() {
  init();
  cin >> n;
  for (int i = 1; i <= n; i++)
    cin >> a[i] >> b[i];
  for (int i = 1; i <= n; i++) {
    d[-a[i] + base][-b[i] + base]++;
  }
  for (int i = 1; i <= 4010; i++) {
    for (int j = 1; j <= 4010; j++) {
      d[i][j] += d[i - 1][j] + d[i][j - 1];
      d[i][j] %= mod;
    }
  }
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    ans += d[a[i] + base][b[i] + base];
    ans %= mod;
  }
  for (int i = 1; i <= n; i++) {
    ans -= C(2 * (a[i] + b[i]), 2 * a[i]);
    ans %= mod;
  }
  ans = (ans * ppow(2, mod - 2, mod) % mod + mod) % mod;
  cout << ans << endl;
  return 0;
}
/*
发现a[i]和b[i]很小,是可以O(n^2)的范围

C(x+y,x)可以看作是(0,0)到(x,y)的方案数,
如果我们要求(0,0)到(x,y)的方案数,
可以令d[i][j]表示(0,0)到(i,j)的方案数,一开始d[0][0]=1.
转移方程为d[i][j]=d[i-1][j]+d[i][j-1].

C(a[i]+a[j]+b[i]+b[j],a[i]+a[j])可以看作:
(0,0)到(a[i]+a[j],b[i]+b[j])的方案数.

枚举i和j是O(n^2)的,显然要优化,
尝试将i和j分开:
将坐标位移一下变为(-a[i],-b[i])到(a[j],b[j])的方案数.
这样i和j就分离了,然后就可以单独计算每个部分的贡献.

观察前面的dp方程,我们一开始令d[0][0]=1,
然后计算出的d[x][y]就是(0,0)到(x,y)的方案数,
那么我们令d[-a[i]][-b[i]]=1,
那么计算出的d[x][y]就是(-a[i],-b[i])到(x,y)的方案数.
这样的话,我们一开始只需要令所有的d[-a[i]][-b[i]]++,
那么计算出来的d[x][y]就是所有数对到达(x,y)的方案数.
因此最后只需要累加d[a[i]][b[i]]即可.
但是存在(-a[i],-b[i])到(a[i],b[i])的方案数,也就是自己和自己匹配,
而题目只需要计算i<j的,因此减掉自己和自己匹配的,方案数为C(2*(a[i]+b[i]),2*a[i]).
最后答案还要再除以2,因为是计算出的是无序的,题目要的是有序的.

参考:
https://www.luogu.com.cn/blog/Ebola-Emperor/solution-at1983
https://www.luogu.com.cn/blog/208529/solution-at1983
*/