[CSP-S模拟测试]:礼物(数学)

题目传送门(内部题80)


输入格式

  第一行输入一个正整数$n$。
  第二行到第$n+1$行每行两个正整数$a_i$和$b_i$表示第$i$个礼物中包含$a_i$个红宝石和$b_i$个绿宝石。


输出格式

  输出一个整数表示方案数。


样例

见下发文件


数据范围与提示

  对于$20\%$的数据:$n\leqslant 5,000,a_i,b_i\leqslant 1,000,000$
  对于另外$30\%$的数据:$a_i,b_i\leqslant 4,000$
  对于另外$20\%$的数据:$n\leqslant 50,000,\sum a_i+\sum b_i\leqslant 2,000,000$
  对于$100\%$的数据:$n\leqslant 100,000,\sum a_i+\sum b_i\leqslant 20,000,000$


题解

转化一下题意,就是求$\sum \limits_{i!=j}C_{a_i+b_i+a_j+b_j}^{a_i+a_j}$。

$$C_{a_i+b_i+a_j+b_j}^{a_i+a_j}=\sum \limits_{t=0}^{a_i+a_j}C_{a_i+b_i}^{t}\times C_{a_j+b_j}^{a_i+a_j-t}=\sum \limits_{t=-a_i}^{a_j}C_{a_i+b_i}^{t+a_i}\times C_{a_j+b_j}^{a_j-t}$$

考虑$t$的枚举范围。当$b_i\leqslant t$时,$C_{a_i+b_i}^{t+a_i}$恒为$0$;当$a_j\leqslant t$时,$C_{a_j+b_j}^{a_j-t}$恒为$0$。所以式子等价于$\sum \limits_{t=-a_i}^{b_i}C_{a_i+b_i}^{t+a_i}\times C_{a_j+b_j}^{a_j-t}$

考虑对于每个$t$,维护出$C_{a_j+b_j}^{a_j-t}$的和,每次新加入一个数,暴力枚举$t$,贡献答案。

时间复杂度:$\Theta(\sum a_i+\sum b_i+n)$。

期望得分:$100$分。

实际得分:$100$分。


代码时刻

#include<bits/stdc++.h>
using namespace std;
const int mod=1000000007;
const int base=20000000;
int n;
int a[100001],b[100001];
long long jc[20000001],inv[20000001],sum[40000001],ans;
long long qpow(long long x,long long y)
{
	long long res=1;
	while(y)
	{
		if(y&1)res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
void pre_work()
{
	jc[0]=1;
	for(int i=1;i<=20000000;i++)jc[i]=jc[i-1]*i%mod;
	inv[20000000]=qpow(jc[20000000],mod-2);
	for(int i=20000000;i;i--)
		inv[i-1]=inv[i]*i%mod;
}
long long C(long long x,long long y)
{
	if(!y)return 1;
	return jc[x]*inv[y]%mod*inv[x-y]%mod;
}
int main()
{
	pre_work();
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&a[i],&b[i]);
	for(int i=1;i<=n;i++)
		for(int j=min(-a[i],-b[i]);j<=max(a[i],b[i]);j++)
		{
			if(a[i]+j>=0)ans=(ans+1LL*C(a[i]+b[i],a[i]+j)*sum[j+base]%mod)%mod;
			if(a[i]-j>=0)sum[j+base]=(sum[j+base]+C(a[i]+b[i],a[i]-j))%mod;
		}
	printf("%lld",(ans<<1)%mod);
	return 0;
}

rp++

posted @ 2019-10-22 20:31  HEOI-动动  阅读(164)  评论(0编辑  收藏  举报