2019牛客国庆集训派对day1 第B题 组合数 解题报告

试题

试题链接

题目描述

给出 n 和 k,求 min ⁡ { n ! k ! ( n − k ) ! , 1 0 18 } \displaystyle \min\left\{\frac{n!}{k! (n - k)!}, 10^{18}\right\} min{k!(nk)!n!,1018} 的值。
其中 n ! = 1 × 2 × ⋯ × n n! = 1 \times 2 \times \cdots \times n n!=1×2××n, 表示 n 的阶乘。

输入描述

输入文件包含多组数据,请处理到文件结束。
每组数据包含两个整数 n 和 k。

  • 0 ⩽ k ⩽ n ⩽ 1 0 9 0 \leqslant k \leqslant n \leqslant 10^9 0kn109
  • 至多 1 0 5 10^5 105 组数据。

输出描述

对于每组数据,输出一个整数,表示所求的值。

样例输入

1000000000 0
1000000000 2
1000000000 500000000

样例输出

1
499999999500000000
1000000000000000000

解题思路

首先,根据杨辉三角形,可知
C m n = C m m − n C_m^n = C_m^{m-n} Cmn=Cmmn

所以, C 10000 9001 \displaystyle C_{10000}^{9001} C100009001 可以弄成 C 10000 999 \displaystyle C_{10000}^{999} C10000999 来算,这样子就快多了。

然后,我们注意到组合数公式:
C m n = A m n n ! = m ! ( m − n ) ! n ! C_m^n = \frac{A_m^n}{n!} = \frac{m!}{(m-n)!n!} Cmn=n!Amn=(mn)!n!m!

再看排列数公式:
A n m = m ! ( m − n ) ! = m ∙ ( m − 1 ) ∙ ( m − 2 ) ∙ ⋯ ∙ ( m − n + 1 ) A_n^m = \frac{m!}{(m-n)!} = m \bullet (m-1) \bullet (m-2) \bullet \cdots \bullet (m-n+1) Anm=(mn)!m!=m(m1)(m2)(mn+1)

例如,
A 9 4 = 9 × 8 × 7 × 6 A_9^4 = 9 \times 8 \times 7 \times 6 A94=9×8×7×6

对比以上两个式子,发现了什么问题呢?

对,组合数公式实际上是可以化简分步进行的。

C m n = m ∙ ( m − 1 ) ∙ ( m − 2 ) ∙ ⋯ ∙ ( m − n + 1 ) n ∙ ( n − 1 ) ∙ ( n − 2 ) ∙ ⋯ ∙ 1 = m 1 ∙ m − 2 2 ∙ m − 3 3 ∙ ⋯ ∙ m − n + 1 n \begin{array}{rcl} C_m^n & = & \displaystyle \frac{m \bullet (m-1) \bullet (m-2) \bullet \cdots \bullet (m-n+1)}{n \bullet (n-1) \bullet (n-2) \bullet \cdots \bullet 1} \\\\ & = & \displaystyle \frac{m}{1} \bullet \frac{m-2}{2} \bullet \frac{m-3}{3} \bullet \cdots \bullet \frac{m-n+1}{n} \end{array} Cmn==n(n1)(n2)1m(m1)(m2)(mn+1)1m2m23m3nmn+1

例如,

C 9 4 = 9 1 × 8 2 × 7 3 × 6 4 C_9^4 = \frac{9}{1} \times \frac{8}{2} \times \frac{7}{3} \times \frac{6}{4} C94=19×28×37×46

这个计算可以循环,也就是

  1. result 赋值为1
  2. 第一轮循环,result 乘上 9 1 \dfrac{9}{1} 19
  3. 第二轮循环,result 乘上 8 2 \dfrac{8}{2} 28
  4. 第三轮循环,result 乘上 7 3 \dfrac{7}{3} 37
  5. 第四轮循环,result 乘上 6 4 \dfrac{6}{4} 46

那么现在,应该知道怎么编写 C m n C_m^n Cmn 的算法了。

Created with Raphaël 2.2.0 开始 输入m与n n是否太大 (超过m的一半) n ← m - n result ← 1 i ← 1 result *= (m-i+1)/i result > 10^18 直接输出10^18 结束 i > n 输出result ++i yes no yes no yes no

源代码

#include <cstdio>

const long long upper_limit = 1000000000000000000LL;

long long C(long long m, long long n)
{
    if (n == 0 || n == m)
        return 1;
    if (n > m / 2)
        n = m - n;
    __int128 res = (__int128)1;
    for (long long i = 1; i <= n; ++i)
    {
        res = res * (m-i+1) / i;
        if (res > upper_limit)
            return upper_limit;
    }
    return (long long)res;
}

int main()
{
    long long n, k;
    while (scanf("%lld%lld", &n, &k) != EOF)
    {
        printf("%lld\n", C(n, k));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值