I 滑稽树上滑稽果(莫队+组合数)

链接: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;
}
原文地址:https://www.cnblogs.com/xiongtao/p/11190914.html