zoukankan      html  css  js  c++  java
  • fzu 2020 组合 组合数对素数取余

    1)根据一下公式,直接计算

         C(n,m) mod p = n*``*(n-m+1)/(1*``*m) mod p

      计算分别分子nn、分母mm中p的个数和对p的余数,若分子中p的个数多余分母中p的个数,则结果为0,

      若不是,则原式变为nn/mm mod p (nn,p)=1,(mm,p)=1

      此时如何求逆元变得至关重要,以下有两种解法。

    2) Lucas 定理:是用来求 C(n,m) mod p的值,p是素数。

      描述为:

      Lucas(n,m,p)=C(n%p,m%p)* Lucas(n/p,m/p,p)

      Lucas(n,0,p)=1;

      而

      C(a,b)=a*(a-1)*```*(a-b+1)/(1*2*```*b)%p,

      其中分子为aa分母为bb,故C(a,b)=aa/bb%p,(此时求法同上)

      至此逆元的求法也很重要,逆元的求法,如下会有所解释。

    补充知识:逆元的求法

       (a/b) mod p=a*(b逆) mod p

         b*x=1(mod p) x就是b的逆元

         而b逆可以利用扩展欧几里德或欧拉函数求得:

          1).扩展欧几里德:b*x+p*y=1 有解,x就是所求

          2).欧拉函数:b^(p-1)=1(mod p),故b*b^(p-2)=1(mod p),所以x=b^(p-2)

    综上所述,其实方法一可以看成是暴力计算,而方法二中只是将大数变为数,再采用方法一的方法计算,这样时间上会有所降低。

    方法一:直接计算,代码如下:

    #include<stdio.h>
    #define LL long long
    int pnum, x, y;
    int getMultMod(int start, int end, int p) {
    int i, j;
    LL res;
    for (i = start, res = 1, pnum = 0; i <= end; i++) {
    if (i % p == 0) {
    j = i;
    while (j % p == 0) {
    pnum++;
    j /= p;
    }
    res = res * j % p;
    } else {
    res = res * i % p;
    }
    }
    return (int) (res % p);
    }
    void extend_gcd(int a, int b) {
    int xx;
    if (b == 0) {
    x = 1, y = 0;
    return;
    }
    extend_gcd(b, a % b);
    xx = x;
    x = y, y = xx - a / b * y;
    }
    void solve(int n, int m, int p) {
    int a, b, apnum, bpnum;
    LL res;
    a = getMultMod(n - m + 1, n, p);
    apnum = pnum;
    b = getMultMod(1, m, p);
    bpnum = pnum;
    if (apnum > bpnum) {
    puts("0");
    return;
    } else {
    extend_gcd(b, p);
    res = (LL) x;
    res = (res % p + p) % p;
    res = res * a % p;
    printf("%I64d\n", res);
    }
    }
    int main() {
    #ifndef ONLINE_JUDGE
    freopen("data.in", "r", stdin);
    #endif
    int t, n, m, p;
    scanf("%d", &t);
    while (t--) {
    scanf("%d %d %d", &n, &m, &p);
    solve(n, m, p);
    }
    return 0;
    }

    根据求逆元的不同也可以对其中的若干方法进行改写如下所示:

    int modular_exp(int a, int b, int p) {
    LL res, temp;
    res = 1 % p, temp = a % p;
    while (b) {
    if (b & 1) {
    res = res * temp % p;
    }
    temp = temp * temp % p;
    b >>= 1;
    }
    return (int) res;
    }
    void solve(int n, int m, int p) {
    int a, b, apnum, bpnum;
    LL res;
    a = getMultMod(n - m + 1, n, p);
    apnum = pnum;
    b = getMultMod(1, m, p);
    bpnum = pnum;
    if (apnum > bpnum) {
    puts("0");
    return;
    } else {
    res = modular_exp(b, p - 2, p);
    res = res * a % p;
    printf("%I64d\n", res);
    }
    }

      方法二:利用Lucas 定理,代码如下所示:

    #include<stdio.h>
    #define LL long long
    int x, y;
    void extend_gcd(int a, int b) {
    int xx;
    if (b == 0) {
    x = 1, y = 0;
    return;
    }
    extend_gcd(b, a % b);
    xx = x;
    x = y, y = xx - a / b * y;
    }
    int C(int a, int b, int p) {
    int i;
    LL resa, resb, res;
    if (b > a) {
    return 0;
    }
    for (i = 0, resa = 1, resb = 1; i < b; i++) {
    resa = resa * (a - i) % p, resb = resb * (b - i) % p;
    }
    extend_gcd(resb, p);
    res = (LL) x;
    res = (res % p + p) % p;
    res = res * resa % p;
    return (int) res;
    }
    void solve(int n, int m, int p) {
    int a, b;
    LL res;
    res = 1;
    while (n || m) {
    a = n % p, b = m % p;
    res = res * C(a, b, p) % p;
    n /= p, m /= p;
    }
    printf("%I64d\n", res);
    }
    int main() {
    #ifndef ONLINE_JUDGE
    freopen("data.in", "r", stdin);
    #endif
    int t, n, m, p;
    scanf("%d", &t);
    while (t--) {
    scanf("%d %d %d", &n, &m, &p);
    solve(n, m, p);
    }
    return 0;
    }


    根据求逆元的不同,以上若干方法又可以改写为:

    int modular_exp(int a, int b, int p) {
    LL res, temp;
    res = 1 % p, temp = a % p;
    while (b) {
    if (b & 1) {
    res = res * temp % p;
    }
    temp = temp * temp % p;
    b >>= 1;
    }
    return (int) res;
    }
    int C(int a, int b, int p) {
    int i;
    LL resa, resb, res;
    if (b > a) {
    return 0;
    }
    for (i = 0, resa = 1, resb = 1; i < b; i++) {
    resa = resa * (a - i) % p, resb = resb * (b - i) % p;
    }
    res = modular_exp(resb, p - 2, p);
    res = res * resa % p;
    return (int) res;
    }


    总结:此类问题,涉及到的方法有,求逆元的方法(扩展欧几里德算法,欧拉函数的知识(有涉及到大数取余)),Lucas定理,小组合数取余。

     

  • 相关阅读:
    FastJson中JSONObject用法
    复盘项目模板
    java Enum 类型
    Java List集合总结
    Spring boot 使用Slf4j 日志
    java.lang.reflect.UndeclaredThrowableException
    Intellij IDEA 中使用 Debug
    Java 13位时间戳转换日期格式
    Java 时间格式转换
    Spring boot 自定义注解
  • 原文地址:https://www.cnblogs.com/xiaoxian1369/p/2205348.html
Copyright © 2011-2022 走看看