分析:
先只考虑两种颜色,假设分别有\(A,B\)个,发现答案为\(\binom{A+B}{A}\)
转化为\(A*B\)的网格上只能向右向下走,从左上角到右下角的方案数
变成三种,分别有\(A,B,C\)个,答案为\(\binom{A+B+C}{A+B}\binom{A+B}{A}\)
把网格变成\(A*B*C\)三维的就好了
两堆积木\(i,j\)的贡献为\((-A_i,-B_i,-C_i)\)到\((A_j,B_j,C_j)\)的方案数
枚举是\(O(n^2)\)的且难以优化
发现实际上\(A,B,C\)的范围很小,只有150
那么网格规模只会有\(m=300\)的大小
于是考虑把所有\((-A_i,-B_i,-C_i)\)的点值都加上1,直接DP求出来最后停留在每个点的方案数
对于每个\(i\)得到停留在\((A_i,B_i,C_i)\)的方案数,最后每个点减去自己到自己的方案再除以2就是答案了
直接暴力DP,复杂度\(O(m^3)\)
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<string>
#define maxn 200005
#define maxm 155
#define INF 0x3f3f3f3f
#define MOD 100000007
#define eps 1e-10
using namespace std;
inline long long getint()
{
long long num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=num*10+c-48,c=getchar();
return num*flag;
}
int n;
int a[maxn],b[maxn],c[maxn];
int f[maxm<<1][maxm<<1][maxm<<1];
int C[maxm<<3][maxm<<3];
inline int upd(int x){return x<MOD?x:x-MOD;}
inline int ksm(int num,int k)
{
int ret=1;
for(;k;k>>=1,num=1ll*num*num%MOD)if(k&1)ret=1ll*ret*num%MOD;
return ret;
}
int main()
{
n=getint();
C[0][0]=1;
for(int i=1;i<maxm<<3;i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++)C[i][j]=upd(C[i-1][j-1]+C[i-1][j]);
}
for(int i=1;i<=n;i++)
{
a[i]=getint(),b[i]=getint(),c[i]=getint();
f[maxm-a[i]][maxm-b[i]][maxm-c[i]]++;
}
for(int i=1;i<maxm<<1;i++)for(int j=1;j<maxm<<1;j++)for(int k=1;k<maxm<<1;k++)
f[i][j][k]=upd(upd(f[i][j][k]+f[i-1][j][k])+upd(f[i][j-1][k]+f[i][j][k-1]));
int ans=0;
for(int i=1;i<=n;i++)ans=(ans+1ll*(f[a[i]+maxm][b[i]+maxm][c[i]+maxm]-(1ll*C[2*(a[i]+b[i]+c[i])][2*(a[i]+b[i])]*C[2*(a[i]+b[i])][2*a[i]]%MOD)+MOD)*ksm(2,MOD-2))%MOD;
printf("%d\n",ans);
}