题目链接:http://codeforces.com/gym/101778/problem/A
题意:有一个坐标轴,现在处在起点(坐标为0),题目给出n和m,问走m步正好到达点坐标为n处的概率,对于在每个点往左往右走的概率相同。
解析:可惜的一题,比赛时思路正确,公式正确,解法正确,但是没想到怎么优化求组合数,所以超时,当然还差些细节。
由于往左往右情况相同,所以先对n求绝对值
1.如果n>m,那么就不可能到达n,此时输出0
2.如果n<=m,此时能够到达n,假设n在左边,假设往左走了l步,往右走了r步,有公式:
①.l+r=m;②l-r=n;可得l=(m+n)/2
那么这种走法的概率就是:C(m,l)*0.5^l*0.5^r,对应题目中概率公式p/q,这里q=2^(l+r);p=C(m,r)
由于涉及求余操作,这里要用到组合数求余方法Lucas定理和除法求余即(1/x)%mod=pow_mod(x,mod-2);
注意:由于情况2时往左走了n步,那么多余的步数(m-n)一定是往左一步对应往右一步的(这样才不会破坏总距离为n的前提),所以(m-n)一定为偶数,否者同情况1一样不能到达n。
最后:在得到p和q之后,由于有公式,所以答案z=p/q,z等于p乘q的除法逆元。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD=1e9+7;
const ll M=2*1e5+5;
ll fact[M],ifact[M];//fact[i]是i的阶乘,ifact[i]是阶乘的除法逆元,两者用于求组合数
ll pow_mod(ll n,ll k,ll mod) //快速幂求n^k余m的结果
{
ll res=1;
n=n%mod;
while(k>0)
{
if(k&1)
res=res*n%mod;
n=n*n%mod;
k>>=1;
}
return res;
}
void init()//初始化
{
fact[0]=ifact[0]=1;
for(int i=1;i<M;++i)
{
fact[i]=(fact[i-1]*i)%MOD;
ifact[i]=pow_mod(fact[i],MOD-2,MOD);
}
}
ll C(ll n,ll m)//求组合数
{
if(n<m)
return 0;
return fact[n]*ifact[m]%MOD*ifact[n-m]%MOD;
}
ll Lucas(ll n,ll m)//Lucas定理求C(n,m)%mod
{
if(m==0)
return 1;
return C(n%MOD,m%MOD)*Lucas(n/MOD,m/MOD)%MOD;
}
int main()
{
init();
ll n,m;
int T;
scanf("%d",&T);
while(T--)
{
scanf("%lld%lld",&n,&m);
n=abs(n);
ll ans;
if(n>m||((m-n)&1))
{
ans=0;
}else
{
ll q=pow_mod(2,m,MOD);
ll p=Lucas(m,m-(m+n)/2);
//cout<<q<<" "<<p<<endl;
ans=p*pow_mod(q,MOD-2,MOD)%MOD;
}
printf("%lld\n",ans);
}
return 0;
}