题意:
解法:
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
code:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=2e5+5;
const int mod=1e9+7;
int fac[maxm],inv[maxm];
int a[maxm],b[maxm];
int d[4444][4444];
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];
const int base=2000;
for(int i=1;i<=n;i++){
d[-a[i]+base][-b[i]+base]++;
}
for(int i=0;i<=4010;i++){
for(int j=0;j<=4010;j++){
if(i-1>=0)d[i][j]+=d[i-1][j];
if(j-1>=0)d[i][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;
}