题意描述
n个不同的滑稽果中,每个滑稽果可取可不取,从所有方案数中选取一种,求选取的方案中滑稽果个数不超过m的概率。(对109+7取模)
输入描述
第一行一个正整数T( T <= 10^5 )
随后T行每行两个整数n,m ( 0 < m <= n <= 10^5 )
输出描述
T行,每行一个整数表示答案。
样例输入
2
5 2
5 1
样例输出
500000004
687500005
解题思路
本题是离线问题,又涉及到区间。我们可以考虑是否能用莫队算法在
O
(
N
N
)
O(N\sqrt{N})
O(NN)时间内解决。而能否用莫队算法的关键在于区间是否能单位转移,以及单位转移是否为常数时间。
首先我们知道该题的答案是
T
=
∑
i
=
1
m
C
(
n
,
i
)
/
2
n
T = \sum_{i = 1}^{m}{C(n,i)}/2^n
T=∑i=1mC(n,i)/2n,其中C(n,r)是组合数,当然我们还需要对P(1e9+7)取模。为了简便起见我们设
S
(
n
,
m
)
=
∑
i
=
1
m
C
(
n
,
i
)
S(n,m) = \sum_{i = 1}^{m}C(n,i)
S(n,m)=∑i=1mC(n,i),那么我们需要考察的是S(n,m)是否能转移到S(n,m+1)、S(n,m-1)、S(n-1,m)、S(n+1,m)。通过对S的展开,以及对组合数公式的运用,我们得出如下转移方程:
S(n,m-1) = S(n,m) - C(n,m)
S(n,m+1) = S(n,m) + C(n,m)
S(n+1,m) = 2 * S(n,m) - C(n,m)
S(n-1,m) = [S(n,m) + C(n-1,m)]/2
通过逆元 [ 1 ] ^{[1]} [1]的知识,我们可以在常数时间内求出C(n,m),于是乎我们发现该题可以采用莫队算法解决。当然上述状态转移时需要注意可能会出现负数取模情况,要处理一下。于是我们最终答案就是S(n,m)/2^n,将上述所有除法转化为模P下的逆元即可。
代码示例
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int P = 1e9+7;
int len = 0; //请尽早初始化
const int N = 1e5+10;
const int Q = N;
typedef long long ll;
ll ans = 2; //Cnr(1,0) + Cnr(1,1) = 2
ll lastAns[Q]; //用来存放最终答案
struct Query{
int l,r,id,block;
Query(){}
Query(int l,int r,int id):l(l),r(r),id(id){
block = l/len;
}
bool operator < (const Query & B) const{
if(block == B.block) return block&1 ? r < B.r : r > B.r;
return block < B.block;
}
}querys[Q];
ll Finv[N],F[N],F2[N];//F存阶乘,Finv存对应逆元,F2[i]为2^i的逆元
ll Qpow(ll x,ll p,ll m){
ll res = 1;
while(p){
if(p&1) res =res*x%m;
x = x*x%m;
p >>= 1;
}
return res;
}
//求阶乘的逆元、2^i的逆元,使得Cnr复杂度为O(1)
void Init(){
F2[0] = F[0] = 1;
for(ll i = 1;i < N;i++)
F[i] = F[i-1]*i%P;
Finv[N-1] = Qpow(F[N-1],P-2,P);
for(int i = N-1;i > 0;i--)
Finv[i-1] = Finv[i]*i%P;
for(int i = 1;i < N;i++)
F2[i] = F2[i-1]*2%P;
for(int i = 1;i < N;i++)
F2[i] = Qpow(F2[i],P-2,P);
}
//返回组合数C(n,m)%P的值
ll Cnr(int n,int m){
if(n < m) return 0;
return F[n]*Finv[n-m]%P*Finv[m]%P;
}
int main(){
int n,m,t;
Init(); //求逆元
len = sqrt(N); //初始化块长度
scanf("%d",&t);
for(int i = 1;i <= t;i++){
scanf("%d%d",&n,&m);
querys[i] = Query(m,n,i);
//m比n小,放在前面速度快一些
}
sort(querys+1,querys+t+1);
int l = 1,r = 1;
for(int i = 1;i <= t;i++){
Query &x = querys[i]; //结构体引用,便于书写
while(l < x.l) ans = (ans+Cnr(r,l+1))%P,l++;
while(l > x.l) ans = (ans-Cnr(r,l)+P)%P,l--;
while(r < x.r) ans = (2*ans-Cnr(r,l)+P)%P,r++;
while(r > x.r) ans = (ans+Cnr(r-1,l))*F2[1]%P,r--;
lastAns[x.id] = ans*F2[r]%P;//不要忘了还要乘2^n的逆元呢
}
for(int i = 1;i <= t;i++) printf("%lld\n",lastAns[i]);
return 0;
}