计蒜客T3144——受力平衡(逆元+数学组合)

题目

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

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

输入格式

输入数据第一行一个正整数 T,表示测试数据组数

接下来 T 行,每行包含两个空格隔开的正整数 n 和 m

输出格式

输出 T 行,每行包括一个数字,表示答案除以 (10^9+7 ) 的余数

数据范围

对于 20% 的数据,m = 1

对于 60% 的数据,1≤ n,m ≤10^3,1≤ T ≤10^4

对于全部的数据,1≤ n,m ≤10^6,1 ≤ T ≤10^6

输出时每行末尾的多余空格,不影响答案正确性

样例输入

1
2 3

样例输出

9

样例解释

不妨用 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​

题解:

1.先看题目用例,气球2个,重物3个,气球我可以用1个,也可以用2个;如果用1个,有2种选择,此时重物有3种选择,共2*3种;如果用2个,气球只有1种选择,而重物有3种选择,共1*3种;共2*3+1*3=9,如果用组合数表示:2.由此推广到气球n个,重物m个,可得答案为:(假设你n<=m)

3.接下来对其进行化简:(如果通过复杂的数学公式推导,本人水平有限,不会)先将原式改写一下:用到组合数基本性质得到

我们在原本的基础上增加了第一项,保持值不变,我们在最后一项减去,并且我们可以发现这一项的值为1。

即为:

为了求出其值,我们建立一个模型:

假设你现在单身,有来自北方的和南方两种风格的女朋友任君挑选,北方的n个人记为A队,南方的m个人记为B队,由于南方的人口比北方多,可知m>=n;现在让你做出一个艰难的决定,让你从中挑选n个人作为你的女朋友,有多少种选法?(如果你是女生,自动脑补为男朋友)

<1>.有最简单的排列组合知识知有:选法

<2>.从另一种角度来看,你对南方或者北方的风格有侧重,可以从南方选k个,剩下(n-k)个从北方选(k从0到n):

这样得到的结果为:

由<1>,<2>知两个结果是相等的,可推出=

接着推出 =,也就是(m+n)!/(m!*n!)-1;

(注:这其实来自于一个非常经典的证明:,证明方法是一样的,这个结果非常重要,可以记住)

4.最重要的解决了,现在是代码实现,由上一步结果知我们需要计算(m+n)的阶乘,有多组测试用例,节省时间,我们需要打表实现阶乘,然后又逆元部分(利用快速幂+费马小定理   可参考博客:https://blog.csdn.net/qq_44616044/article/details/107252092)可得出(m+n)!/(m!*n!)%mod=(m+n)!*pow(m!,mod-2)*pow(n!,,mod-2)%mod,由于数比较大,所以需要每一步mod上mod,则结果为{[(m+n)!*pow(m!,mod-2)]%mod*pow(n!,mod-2)}%mod,然后用快速幂替换幂运算;

AC代码

#include<stdio.h>

#define ll long long
const int mod = 1e9+7;
ll md[2000005];  //n+m

ll quick_pow(ll x,ll n,ll m)
{
    ll res = 1;
    while(n > 0)
    {
        if(n & 1)
            res = res * x % m;
        x = x * x % m;
        n >>= 1;
    }
    return res;
}

int main()
{
    ll m,n;
    md[0]=1;
    for(int i=1;i<=2000000;i++)
    {
        md[i]=(md[i-1]*i)%mod;//每次%mod,防止超出long long范围,不改变最终结果
    }//打表

    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);//这题时间卡的特别紧,我用cin关了同步也会超时
        ll ans=((md[n+m]*quick_pow(md[n],mod - 2,mod)))%mod*quick_pow(md[m],mod - 2,mod)%mod; //一定要每一步%mod
        printf("%lld\n",ans-1);
    }
    return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值