题干
有 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
C21∗C31+C22∗C32=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
C41∗C31+C42∗C32+C43∗C33
… …
可以发现,每种方案中气球与重物的个数必须相同,个数依次从1到min(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
Cn1∗Cm1+Cn2∗Cm2+...+Cnn∗Cmn
此公式的结果即为答案
直接套公式累加肯定是会超时的 那么接下来该怎么算呢?
笔者特地搜了一下,发现有一个之前从来没有学过的公式:
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}
Cmr∗Cn0+Cmr−1∗Cn1+Cmr−2∗Cn2+⋅⋅⋅+Cm0∗Cnr=Cn+mr,r≤min{m,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
Cmr∗Cn0+Cmr−1∗Cn1+Cmr−2∗Cn2+⋅⋅⋅+Cm0∗Cnr=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 Cmn∗Cn0+Cmn−1∗Cn1+Cmn−2∗Cn2+⋅⋅⋅+Cm0∗Cnn=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}) Cmn∗Cnn+Cmn−1∗Cnn−1+Cmn−2∗Cnn−2+⋅⋅⋅+Cm0∗Cn0=Cn+mn(因为Cab=Caa−b)
这样看起来就非常整齐了,组合数上标从0开始一直到m与n的最小值
- 这个公式要灵活运用,只要有组合数 C m i ∗ C n i C_m^i∗C_n^i Cmi∗Cni ( i取非负整数,不超过min{m,n} ),就可以直接想这个公式。
而且和本题的公式非常相似,仅仅多了一个
C
m
0
∗
C
n
0
C_m^0∗C_n^0
Cm0∗Cn0 ,因此把
C
m
0
∗
C
n
0
=
1
C_m^0∗C_n^0=1
Cm0∗Cn0=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
Cn1∗Cm1+Cn2∗Cm2+...+Cnn∗Cmn=Cn+mn−1
又
C
n
+
m
n
−
1
=
(
n
+
m
)
!
n
!
∗
m
!
−
1
C_{n+m}^n-1=\dfrac{(n+m)!}{n!*m!}-1
Cn+mn−1=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