题目来源:第八届ACM山东省赛
题目链接:http://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Index/problemdetail/pid/3895.html
题目大意:在xi处有ci个烟火,每秒钟向前后位置分裂,问T秒后在w位置上有多少烟火。
解题思路:由烟火爆炸的过程可以联想到杨辉三角(如下图),即以m秒每个位置的烟火数为第m行,从x位置爆炸到x-1和x+1的位置,可以对比为杨辉三角中每一行的数字等于上一行数字的左右两个数字之和。由杨辉三角的性质“第n行的m个数可表示为C(n-1,m-1),即为从n-1个不同元素中取m-1个元素的组合数”可以把题目转换为组合数问题。
附图:(以初始位置为中心)
0 0 0 0 0 1 0 0 0 0 0 (0s)
0 0 0 0 1 0 1 0 0 0 0
0 0 0 1 0 2 0 1 0 0 0
0 0 1 0 3 0 3 0 1 0 0
0 1 0 4 0 6 0 4 0 1 0
(去掉0即为杨辉三角)
由于组合数较大,我们可以用求逆元的方式求组合数。组合数的计算方法为C(n,m)=n!/(m!*(n-m)!)。我们记a的逆元为inv(a)。
求逆元的方法:
inv(a)=a^(p-2)%p;
组合数与逆元之间的关系:
(a/b)%p=[a*inv(a)]%p=[a%p*inv(b)%p]%p;
a/bc=(a/b)/c=[(a/b)*inv(c)]%p={[(a%p*inv(b)%p)%p]*inv(c)%p}%p;
算法实现:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int maxn=1e5;
ll abc[101010]; //与初始点距离有关的一个数组
ll fac[101010];
void init() //求阶乘
{
fac[0]=1;
for(int i=1; i<=maxn; i++)
{
fac[i]=fac[i-1]*i;
fac[i]=fac[i]%mod;
}
}
ll quickM(ll a, ll b) //快速幂
{
ll c=a%mod, ans=1;
while(b)
{
if(b&1) //b的二进制与上1
ans=ans*c%mod;
c=c*c%mod;
b=b>>1;
}
return ans;
}
ll C(int n, int m) //用逆元法求组合数
{
return (fac[n]*quickM(fac[m], mod-2)%mod*quickM(fac[n-m],mod-2)%mod)%mod;
} //inv(a)=quickM(a,mod-2)%mod
int main()
{
int n,T,w,xi,ci;
init();
while(cin>>n>>T>>w)
{
memset(abc,0,sizeof(abc));
for(int i=T,k=T; i>=0; i=i-2,k--)
abc[i]=C(T,k); //i为距初始点位置,abc[i]为每个烟火的所求值
ll ans=0;
for(int i=1; i<=n; i++)
{
cin>>xi>>ci;
ans=ans+ci*abc[(int)abs(w-xi)];
ans=ans%mod;
}
cout<<ans<<endl;
}
}