计蒜客T3144----受力平衡(乘法逆元应用)

题干
有 n 个不同的气球和 m 个不同的重物,每个气球都可以提供 1N 的升力(竖直向上),每个重物都会受到 1N 的重力(竖直向下)。

现要选出若干个气球和若干个重物,将他们固定在一起,并且使得固定之后的整体受力平衡,请问共有多少种满足条件的方案?

输入格式

输入数据第一行一个正整数 T,表示测试数据组数。
接下来 T行,每行包含两个空格隔开的正整数 n 和 m

输出格式

输出 T行,每行包括一个数字,表示答案除以 109+7的余数

数据范围

对于 20%的数据,m=1
对于 60%的数据,1≤n,m≤103,1≤T≤104
对于全部的数据,1≤n,m≤106,1≤T≤106

Sample Input

1
2 3

Sample Output

9

Sample Explain

不妨用 b1,b2表示两个气球,用 w1,w2,w3表示三个重物
下面每行都描述了一个符合要求的方案

b1,w1

b1,w2

b1,w3

b2,w1

b2,w2

b2,w3

b1,b2,w1,w2

b1,b2,w1,w3

b1,b2,w2,w3

思路:

先以样例中的2个重物、3个气球为例
方案的种类数为:
C 2 1 ∗ C 3 1 + C 2 2 ∗ C 3 2 = 9 C_2^1*C_3^1 + C_2^2*C_3^2=9 C21C31+C22C32=9
再以4个重物、3个气球为例
方案的种类数为:
C 4 1 ∗ C 3 1 + C 4 2 ∗ C 3 2 + C 4 3 ∗ C 3 3 C_4^1*C_3^1 + C_4^2*C_3^2+C_4^3*C_3^3 C41C31+C42C32+C43C33
… …

可以发现,每种方案中气球与重物的个数必须相同,个数依次从1min(m,n)

故得到公式:(不妨令n<=m)
C n 1 ​ ∗ C m 1 ​ + C n 2 ​ ∗ C m 2 ​ + . . . + C n n ​ ∗ C m n ​ ​ C_n^1​∗C_m^1​+C_n^2​∗C_m^2​+...+C_n^n​∗C_m^n​​ Cn1Cm1+Cn2Cm2+...+CnnCmn

此公式的结果即为答案

直接套公式累加肯定是会超时的 那么接下来该怎么算呢?



笔者特地搜了一下,发现有一个之前从来没有学过的公式:
C m r ​ ∗ C n 0 ​ + C m r − 1 ​ ∗ C n 1 ​ + C m r − 2 ∗ C n 2 + ⋅ ⋅ ⋅ + C m 0 ​ ∗ C n r = C n + m r , r ≤ m i n { m , n } C_m^r​∗C_n^0​+C_m^{r-1}​∗C_n^1​+C_m^{r-2}*C_n^2+···+C_m^0​∗C_n^r=C_{n+m}^r,r≤min{m,n} CmrCn0+Cmr1Cn1+Cmr2Cn2++Cm0Cnr=Cn+mrrminm,n
要证明此公式很简单(会证明的大佬就不用看这里了嗷QwQ)

  • 先看式子左边,我们发现,每一项都是从m+n个不同元素中选出r个元素的方案数;而式子右边恰恰是 C n + m r C_{n+m}^r Cn+mr,即从m+n个元素中选出r个元素的总方案数。

r恰好等于m和n的最小值时,我们把这个公式的左边优化一下:
(假设r=n)
C m r ​ ∗ C n 0 ​ + C m r − 1 ​ ∗ C n 1 ​ + C m r − 2 ∗ C n 2 + ⋅ ⋅ ⋅ + C m 0 ​ ∗ C n r = C n + m r C_m^r​∗C_n^0​+C_m^{r-1}​∗C_n^1​+C_m^{r-2}*C_n^2+···+C_m^0​∗C_n^r=C_{n+m}^r CmrCn0+Cmr1Cn1+Cmr2Cn2++Cm0Cnr=Cn+mr

C m n ​ ∗ C n 0 ​ + C m n − 1 ​ ∗ C n 1 ​ + C m n − 2 ∗ C n 2 + ⋅ ⋅ ⋅ + C m 0 ​ ∗ C n n = C n + m n C_m^n​∗C_n^0​+C_m^{n-1}​∗C_n^1​+C_m^{n-2}*C_n^2+···+C_m^0​∗C_n^n=C_{n+m}^n CmnCn0+Cmn1Cn1+Cmn2Cn2++Cm0Cnn=Cn+mn

C m n ​ ∗ C n n ​ + C m n − 1 ​ ∗ C n n − 1 ​ + C m n − 2 ∗ C n n − 2 + ⋅ ⋅ ⋅ + C m 0 ​ ∗ C n 0 = C n + m n ( 因 为 C a b = C a a − b ) C_m^n​∗C_n^n​+C_m^{n-1}​∗C_n^{n-1}​+C_m^{n-2}*C_n^{n-2}+···+C_m^0​∗C_n^0=C_{n+m}^n(因为C_a^b=C_a^{a-b}) CmnCnn+Cmn1Cnn1+Cmn2Cnn2++Cm0Cn0=Cn+mnCab=Caab

这样看起来就非常整齐了,组合数上标从0开始一直到m与n的最小值

  • 这个公式要灵活运用,只要有组合数 C m i ​ ∗ C n i C_m^i​∗C_n^i CmiCni ( i取非负整数,不超过min{m,n} ),就可以直接想这个公式。

而且和本题的公式非常相似,仅仅多了一个 C m 0 ​ ∗ C n 0 C_m^0​∗C_n^0 Cm0Cn0 ,因此把 C m 0 ​ ∗ C n 0 = 1 C_m^0​∗C_n^0=1 Cm0Cn0=1 减掉后,就可以直接应用上述公式。
C n 1 ​ ∗ C m 1 ​ + C n 2 ​ ∗ C m 2 ​ + . . . + C n n ​ ∗ C m n = ​ ​ C n + m n − 1 C_n^1​∗C_m^1​+C_n^2​∗C_m^2​+...+C_n^n​∗C_m^n=​​C_{n+m}^n-1 Cn1Cm1+Cn2Cm2+...+CnnCmn=Cn+mn1
C n + m n − 1 = ( n + m ) ! n ! ∗ m ! − 1 C_{n+m}^n-1=\dfrac{(n+m)!}{n!*m!}-1 Cn+mn1=n!m!(n+m)!1
故只需求出 ( n + m ) ! n ! ∗ m ! − 1 \dfrac{(n+m)!}{n!*m!}-1 n!m!(n+m)!1即可

求阶乘的话优先打表,可以节省时间(题目上要求取模,在打表的时候不要忘记)
先求出分子,然后乘上分母的逆元,取模。分母有n!和m!,则分两次处理。

代码来咯!!!

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
ll fact[1000005*2]; //factorial阶乘数组,n+m=2000000,所以开2倍

//求逆元这里用了费马小定理
ll quick_pow(ll x, ll n, ll mod)
{
    ll res=1;
    while( n > 0 )
    {
        if(n & 1)
            res=res * x % mod ;
        x=x * x % mod;
        n >>= 1;
    }
    return res;
}
ll inv(ll a)
{
    return quick_pow(a,mod-2,mod);
}

int main()
{
    int t;
    scanf("%d",&t);
    //阶乘打表
    fact[0]=1; //0!=1
    for(int i=1 ; i<=2000000 ; i++)
        fact[i]=( fact[i-1]*i ) % mod;
    ll m,n;
    while(t--)
    {
        scanf("%lld%lld",&n,&m);
        ll ans=( (fact[n+m]*inv(fact[n])) % mod * inv(fact[m]) )%mod ;//一定要对每一步都求模,少一个就不行
        printf("%lld\n",ans-1); //千万别忘了要-1!!!
    }
    return 0;
}

附:排列组合各种公式大全:
https://wenku.baidu.com/view/9cc93ef4b8f3f90f76c66137ee06eff9aef849bb.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值