Acm学习日程—六月二十三

求 \sum \bigl(\begin{smallmatrix}n \\ m \end{smallmatrix}\bigr)的前m项和

一、快速求组合数取模C(n, m)%p

1. n很小,p随意,p不需要为素数

1) 原理

使用杨辉三角:Cmn%p=(Cm−1n−1+Cmn−1)%pCnm%p=(Cn−1m−1+Cn−1m)%p 
组合数C(n, m)其实就是杨辉三角第n行第m列的值(下标从0开始算的话)。每一行的各个值都是迭代上一行的结果。那么用二维数组打个表即可,for里套个for。

2) 我的模板

typedef long long lld;
const int maxn = 1000+10;

lld C_arr[maxn+10][maxn+10];

void C_init(int n, int pr) {
    for (int i = 0; i <= n; i++) {
        C_arr[i][0] = C_arr[i][i] = 1;
        for (int j = 1; j < i; j++)
            C_arr[i][j] = (C_arr[i - 1][j - 1] + C_arr[i - 1][j]) % pr;
    }
}

lld C(int n, int m) {
    return C_arr[n][m];
}

 

 

2. n相对小(方便打表),p可以很大,p要求为素数

1) 原理

仅使用费马小定理: 若p是质数,且a,p互质,那么 a的(p-1)次方除以p的余数恒等于1, 即a^(p-1) ≡ 1 (mod p),所以a^(p-2) ≡ 1/a (mod p)。所以a的逆元为a^(p-2)。于是将n!m!(n−m)!n!m!(n−m)! 中的除法全变成了乘法: 
得到公式:Cmn%p=((n!)%p∗[(n−m)!]p−2%p∗(m!)p−2%p)%pCnm%p=((n!)%p∗[(n−m)!]p−2%p∗(m!)p−2%p)%p

2) 我的模板

lld pow_mod(lld a, lld b, const int &pr)
{
    lld ans = 1;
    while (b) {
        if (b & 1) ans = ans * a % pr;
        b >>= 1;
        a = a * a % pr;
    }
    return ans;
}   //求a的b次方,将b用二进制表示

lld C(int n, int m)
{
    return fac(n) % p * pow_mod(fac(n - m), p - 2, p) * pow_mod(fac(m), p - 2, p);
}

可以看到这里最麻烦的是求阶乘fac(n),如果n不大的话打表是极好的。n较大的话使用以下公式递归求得: 
n!=n!(n2)!n2)!∗[(n2)!]2=Cn/2n∗[(n2)!]2n!=n!(n2)!n2)!∗[(n2)!]2=Cnn/2∗[(n2)!]2 
具体以后再写一篇求阶乘。

lld C_small(lld n, lld m, const int &pr)
{
    lld ans = 1;
    for (int i = 1; i <= m; i++)
    {
        lld a = (n - m + i) % pr;
        lld b = i % pr;
        ans = ans * (a * pow_mod(b, pr - 2, pr) % pr) % pr; //Fermat Theory
    }
    return ans;
}

3. n很大时要求p较小(p<10^5),p要求为素数

1) 原理

使用Lucas定理:Cmn%p=(Cm%pn%pCm/pn/p)%pCnm%p=(Cn%pm%pCn/pm/p)%p 
为什么要求p挺小,由公式就可以看出,p太大了的话Cm%pn%pCn%pm%p也依然很大。Lucas定理用到了费马小定理,要求p为素数。对于每个Cm/pn/pCn/pm/p,递归调用Lucas定理。可以看见n被p取模后很容易就变小了,所以要求p较小。 
定理证明:网上看到的大神的博客

2) 我的模板

typedef long long lld;

lld pow_mod(lld a, lld b, const int &pr)
{
    lld ans = 1;
    while (b) {
        if (b & 1) ans = ans * a % pr;
        b >>= 1;
        a = a * a % pr;
    }
    return ans;
}

lld C_small(lld n, lld m, const int &pr)
{
    lld ans = 1;
    for (int i = 1; i <= m; i++)
    {
        lld a = (n - m + i) % pr;
        lld b = i % pr;
        ans = ans * (a * pow_mod(b, pr - 2, pr) % pr) % pr; //Fermat Theory
    }
    return ans;
}

lld C(lld n, lld m, const int &pr) // Lucas's theorem
{
    if (m == 0 || m == n) return 1;
    return C_small(n % pr, m % pr, pr) * C(n / pr, m / pr, pr) % pr;
}

C_small就是用求逆元求解,像法二一样做打表也是极好的。 
如果n不大,p很大,用一下Lucas定理后也就相当于执行了法二,所以以后直接用Lucas即可。

杭电Harvest of Apples解法

#include <bits/stdc++.h>
using namespace std;
//最后用了莫队算法

const int N = 200000;
const int MOD = 1000000007;
struct query
{
    int n, k, i;
} Q[N];
int T;
vector <query> lst[N];
int cnt, mx, chunk;
int fac[N], inv[N], res[N], in_chunk[N]; //inv[n]为fac[n]的逆元
int powi(int a, int b)  //求a的b 
{
    int c = 1;
    for (; b; b >>= 1, a = 1ll * a * a % MOD)
        if (b & 1) c = 1ll * c * a % MOD;
    return c;
}
int C(int a, int b)
{
    return 1ll * fac[a] * inv[b] % MOD * inv[a - b] % MOD;
}
int comp(query a, query b)
{
    return a.n < b.n;
}
int main()
{
    mx = 100000;
    fac[0] = 1; for (int i = 1; i <= mx; ++ i) fac[i] = 1ll * fac[i - 1] * i % MOD;
    inv[mx] = powi(fac[mx], MOD - 2); for (int i = mx - 1; ~i; -- i) inv[i] = 1ll * inv[i + 1] * (i + 1) % MOD;   //公式推导
    chunk = sqrt(mx);
    cnt = 1;
    for (int i = 1; i <= mx; i += chunk, ++ cnt)
        for (int j = i; j < i + chunk && j <= mx; ++ j)
            in_chunk[j] = cnt;
    cnt --;
    scanf("%d", &T);
    for (int i = 1; i <= T; ++ i)    //莫队算法
    {
        scanf("%d%d", &Q[i].n, &Q[i].k), Q[i].i = i;
        lst[in_chunk[Q[i].k]].push_back(Q[i]);
    }
    for (int i = 1; i <= cnt; ++ i) if (lst[i].size())
    {
        sort(lst[i].begin(), lst[i].end(), comp);
        int val = 0, in = lst[i][0].n, ik = -1;
        for (int j = 0; j < lst[i].size(); ++ j)
        {
            while (in < lst[i][j].n) val = (0ll + val + val + MOD - C(in ++, ik)) % MOD;
            while (ik < lst[i][j].k) val = (val + C(in, ++ ik)) % MOD;
            while (ik > lst[i][j].k) val = (val + MOD - C(in, ik --)) % MOD;
            res[lst[i][j].i] = val;
        }
    }
    for (int i = 1; i <= T; ++ i) printf("%d\n", res[i]);
}

转载:https://blog.csdn.net/xienaoban/article/details/65448521

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值