呦,来一次久违的BBQ吧!
AT题…日本的题库质量一向很高
这题是有关组合数的DP…
题目中的问题:
∑
i
=
1
n
∑
j
=
i
+
1
n
(
a
i
+
b
i
+
a
j
+
b
j
a
i
+
a
j
)
\sum_{i=1}^{n}\sum_{j=i + 1}^{n}{a_i+b_i+a_j+b_j \choose a_i+a_j}
∑i=1n∑j=i+1n(ai+ajai+bi+aj+bj)
提出
(
a
i
+
b
i
+
a
j
+
b
j
a
i
+
a
j
)
{a_i+b_i+a_j+b_j \choose a_i+a_j}
(ai+ajai+bi+aj+bj)这部分
感性理解后,可以发现这是原点到
(
a
i
+
b
i
,
a
j
+
b
j
)
(a_i+b_i,a_j+b_j)
(ai+bi,aj+bj)的路径的个数.
但是这样就会想到O(N2)枚举,但是这样显然是T的,所以我们还要继续优化这个式子
将原点平移可以理解为
(
a
i
,
a
j
)
(a_i,a_j)
(ai,aj)到
(
b
i
,
b
j
)
(b_i,b_j)
(bi,bj)的路径个数
是不是觉得很好处理了.
但是
∑
i
=
1
n
∑
j
=
i
+
1
n
\sum_{i=1}^{n}\sum_{j=i + 1}^{n}
∑i=1n∑j=i+1n中并没有自己到自己的条数,所以需要减去这些多出来的条数
这里如果使用杨辉三角预处理要O(40002*2)感觉十分的不保险,所以就要使用到快速计算组合数.
#include<bits/stdc++.h>
#define rap(i,first,last) for(int i=first;i<=last;++i)
using namespace std;
const int maxN=2005;
const int mod=1e9+7;
const int inv2=500000004;//预处理2的逆元
long long fac[maxN*4+100],inv[maxN*4+100];
long long dp[maxN*2+100][maxN*2+100],N;
int A[1000005],B[1000005];
long long C(int n,int m)//计算组合数
{
return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int main()
{
//预处理组合数
fac[0]=inv[0]=inv[1]=1;
rap(i,1,maxN*4)fac[i]=1ll*fac[i-1]*i%mod;
rap(i,2,maxN*4)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
rap(i,1,maxN*4)inv[i]=1ll*inv[i-1]*inv[i]%mod;
scanf("%d",&N);
rap(i,1,N)
{
scanf("%d%d",&A[i],&B[i]);
dp[maxN-A[i]][maxN-B[i]]++;//在-A[i],-B[i]位置加一
}
rap(i,-maxN+1,maxN)
rap(j,-maxN+1,maxN)
{
dp[maxN+i][maxN+j]+=dp[maxN+i-1][maxN+j]+dp[maxN+i][maxN+j-1];//类似杨慧三角的DP
dp[maxN+i][maxN+j]%=mod;
}
long long answer=0;
rap(i,1,N)
{
answer+=dp[maxN+A[i]][maxN+B[i]];//对答案加上这个位置的方案数
answer%=mod;
answer-=C(A[i]*2+B[i]*2,A[i]*2);//减去自己到自己的方案数
answer=(answer+mod)%mod;
}
answer*=inv2;//可以发现多计算了一遍,需要除二,在膜域中也就是乘上2的逆元
answer%=mod;
printf("%lld\n",answer);//输出answer
return ~0;
}
看似一道简单的DP,其中涉及了各种方面的知识点只有对膜域和组合数掌握的很好才能在做这类题时得心应手.