链接:https://ac.nowcoder.com/acm/contest/992/I
来源:牛客网
题目描述
n个不同的滑稽果中,每个滑稽果可取可不取,从所有方案数中选取一种,求选取的方案中滑稽果个数不超过m的概率。(对109+7取模)
输入描述:
第一行一个正整数T( T <= 10^5 )
随后T行每行两个整数n,m ( 0 < m <= n <= 10^5 )
输出描述:
T行,每行一个整数表示答案。
示例1
输入
2 5 2 5 1
输出
500000004 687500005
思路:每个果子有取与不取两种能,所以有2^n种可能,而不超过取m个的概率是 C(n,m)+C(n,m-1)+...+C(n,0)
这里令 S(n,m)= C(n,m)+C(n,m-1)+...+C(n,0);答案就是 S(n,m)/2^n % mod
暴力写是会超时的,因为测试样例 T<=1e5,n<=1e5.
很明显测试样例太多,所以要采用离线的算法,就会想到莫队。那就要求知道小于和大于n,m时的关系
因为:
S(n,m)=C(n,0)+C(n,1)+...+C(n,m-1) +C(n,m)=S(n,m-1)+C(n,m) 这样就可以得到大于小于m的转换关系
因为: C(n,m) = C(n-1,m)+C(n-1,m-1)
C(n,m-1) = C(n-1,m-1)+C(n-1,m-2)
.... ....
C(n,1) = C(n-1,0)+C(n-1,1)
C(n,0) = C(n-1,0) ==1
上式叠加得 S(n,m)=2*S(n-1,m)-C(n-1,m) 这样就可以的到大于小于n的转化关系。
当然期间有除法再去摸,明显要用逆元,这里用的是费马小定理
代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<cmath> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; const int maxn=1e5+10; const int mod=1e9+7; struct node{ int n,m,id; }a[maxn]; ll f[maxn],nif[maxn],zu[maxn],ans[maxn]; ll q_pow(ll a,ll b)//快速幂,用于费马小定理 { ll ans=1; while(b) { if(b&1) ans=(ans*a)%mod; a=(a*a)%mod; b>>=1; } return ans; } void init() { f[1]=1; int black=sqrt(maxn); for(int i=2;i<maxn;i++)//阶乘 f[i]=(f[i-1]*i)%mod; for(int i=1;i<maxn;i++) zu[i]=(i-1)/black+1; for(int i=1;i<maxn;i++)//阶乘的逆元 nif[i]=q_pow(f[i],mod-2); } bool cmp(node a1,node a2) { if(zu[a1.n]!=zu[a2.n]) return zu[a1.n]<zu[a2.n]; return a1.m<a2.m; } ll cal(ll n,ll m)//C(n,m) { if(m>n||n==0||n<0) return 0; if(m==n||m==0) return 1; return ((f[n]*nif[m])%mod*nif[n-m])%mod; } int main() { init(); int t; scanf("%d",&t); for(int i=1;i<=t;i++) { scanf("%d%d",&a[i].n,&a[i].m); a[i].id=i; } sort(a+1,a+t+1,cmp); ll N=1,M=1,sum=2;//s(1,1)=2 for(int i=1;i<=t;i++) { while(N<a[i].n) sum=(2*sum-cal(N++,M)+mod)%mod; while(N>a[i].n) sum=((sum+cal(--N,M))%mod*nif[2])%mod; while(M<a[i].m) sum=(sum+cal(N,++M))%mod; while(M>a[i].m) sum=(sum-cal(N,M--)+mod)%mod; ans[a[i].id]=(sum*q_pow(q_pow(2,a[i].n),mod-2))%mod; } for(int i=1;i<=t;i++) printf("%lld ",ans[i]); return 0; }