Lightoj 1067(逆元)

Given n different objects, you want to take k of them. How many ways to can do it?

For example, say there are 4 items; you want to take 2 of them. So, you can do it 6 ways.

Take 1, 2
Take 1, 3
Take 1, 4
Take 2, 3
Take 2, 4
Take 3, 4

Input
Input starts with an integer T (≤ 2000), denoting the number of test cases.

Each test case contains two integers n (1 ≤ n ≤ 106), k (0 ≤ k ≤ n).

Output
For each case, output the case number and the desired value. Since the result can be very large, you have to print the result modulo 1000003.

Sample Input
3
4 2
5 0
6 4
Sample Output
Case 1: 6
Case 2: 1
Case 3: 15

两种方法可求解,不过感觉大致上是差不多的。。。

题意:数学组合问题改编成费马小定理—>逆元。。。组合 C(n,m) = n! / (m! * (n - m)! )
逆元:由费马小定理a ^ (p-1) = 1 (%p),两边同时除去a——a ^ (p-2) = a^-1 (%p),得a的逆元a^-1 = a ^ (p-2)。
题解:当n 不大于 1e6 时可以考虑打表,先把每个数的介乘打表,再把每个数的逆元打表,然后就出来答案了

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define M 1000003
#define LL long long

LL pre[M], pows[M];
void init()//将每个数的介乘打表 
{
    pre[0] = 1;
    for(int i=1; i<M; i++)
    {
        pre[i] = pre[i-1] * i % M;
    }
} 

LL quickpow(LL n, LL m, LL k)//用快速幂求逆元 
{
    LL res = 1;
    while(m)
    {
        if(m & 1)
        {
            res = res * n % k;
        }
        m >>= 1;
        n = n * n % k;
    }
    return res;
}
void init2()//将每个数的介乘的逆元打表 
{
    pows[0] = 1;
    pows[1] = 1;
    for(LL i=2; i<M; i++)
    {
        pows[i] = quickpow(pre[i], M-2, M);//传入数据是每个数的介乘 
    }
}
int main()
{
    int t;
    LL ans, n, m, a, b, c;
    scanf("%d", &t);
    init();
    init2();
    for(int ca=1; ca<=t; ca++)
    {
        scanf("%lld%lld", &n, &m);
        a = pre[n];
        b = pows[m];
        c = pows[n-m];
        ans = (a * c % M) * b % M;
        printf("Case %d: %lld\n", ca, ans);
    }

    return 0;
}

lucas定理求解。。。。。lucas大模板题
根据百度可知:C(n,m)%p=C(n/p,m/p)*C(n%p,m%p)%p链接在此
然后就可以开始我们构造函数之路了

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
#define M 1000003

LL fac[M];
void fact()//构造阶乘
{
    fac[0] = 1;
    for(int i=1; i<M; i++)
    {
        fac[i] = fac[i-1] * i % M;
    }
}

LL quickpow(LL n, LL m, LL k)//快速幂
{
    LL res = 1;
    while(m)
    {
        if(m & 1)
        {
            res = res * n % k;
        }
        m >>= 1;
        n = n * n % k;
    }
    return res;
}
LL comb(LL n, LL m, LL k)//这个是组合函数
{
    LL ans = 1;
    ans = fac[n] * quickpow(fac[m]*fac[n-m]%k, k-2, k) % k;
    return ans;
}

LL lucas(LL n, LL m, LL k)//这个是lucas
{
    LL ans = 1;
    if(m > n)   return 0;//这个应是显然咯 
    while(n && m && ans)
    {
        ans = ans * comb(n%k, m%k, k);
        n /= k;
        m /= k;
    }
    /*
    return comb(n%k, m%k, k) * lucas(n/k, m/k, k);递归方法 
    */
    return ans;
}

int main()
{
    int t;
    LL n, m;
    fact();
    scanf("%d", &t);
    for(int ca=1; ca<=t; ca++)
    {
        scanf("%lld%lld", &n, &m);
        printf("Case %d: %lld\n", ca, lucas(n, m, M));
    }

    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值