组合数——Lucas 基础学习整理【陆续更新】


声明:博主是个菜比,如果哪里写错了,请留言说明,谢谢,部分参考acdreamers大牛


定义 :

L u c a s C n m % p p

推导

这里写图片描述
这里写图片描述

这个自行百度吧,和二项式有关,下面看下结论吧(我也不懂)

结论

已知: C n m % p

这里写图片描述

逆元可以使用费马大定理或者扩展欧几里得都行

经典例题:

[1] HDU 3037 Saving Beans

题意 :有n颗不同的树,每棵树的豆子无限多,问你有多少种方法可以保存不超过m个豆子(它们是相同的)。
分析:首先我们利用组合数中的隔板法来求出每个m的方案,然后求和即可

答案应该是 {n棵树取m个豆子} + {n棵树取m - 1} + {n棵树取m - 2} + …..+ {n棵树取0}
对于每个子问题我们利用隔板法来求解,比如第一个子问题 -> {n棵树取m个豆子}
我们把n棵树也当做豆子,然后在n + m个豆子中选出 m - 1个豆子来当做板子来分割,方案为 C n + m 1 m 1
同理,我们把若干子问题这样求出,答案依次为:
a n s = C n 0 + C n + 1 1 + · · · + C n + m 2 m 2 + C n + m 1 m 1 = C n + m m
上式是由 C n m = C n 1 m + C n 1 m 1 推得

然后就利用Lucas来求即可

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>

using namespace std;

#define mod(x) ((x) % p)
typedef long long ll;
const int maxn = 1e5 + 10;

ll fac[maxn + 10],rfac[maxn + 10];
ll n,m,p;

ll qpow(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res = mod(res * 1ll * a);
        a = mod(a * 1ll * a);
        b >>= 1;
    }
    return res;
}

ll C(ll nn, ll mm) {
    ll up = 1, down = 1;
    for (int i = nn - mm + 1; i <= nn; i++) up = mod(up * 1ll * i);
    for (int i = 1; i <= mm; i++) down = mod(down * 1ll * i);
    return mod(up * qpow(down, p - 2));
}

ll lucas(ll nn, ll mm) {
    if (mm == 0) return 1;
    return mod(lucas(nn / p, mm / p) * 1ll * C(nn % p, mm % p));
}


int main() {
    int T; scanf("%d", &T);
    while (T--) {
        scanf("%lld%lld%lld", &n, &m, &p);
        printf("%lld\n", lucas(n + m, m));
    }
    return 0;
}

[2] HDU - 3944 DP?

题意: 已知在杨辉三角中,给你一个位置(简称 C n k ),问你从(0,0)(n,k)经过的数字和最小,结果对P取模,每次可以往下走,或者往右下走。

分析 : 首先杨辉三角是左右对称的,如果所求的k > n / 2时,直接令k = n - k;即可,然后我们只考虑在一侧的情况即可,我们这样想如果k == 0时,我们一直往下走即可,假设n = 5,如果k = 1时,我们肯定优先往下走,然后到了n - 1的位置,再往右下走即可 简图如下 :
这里写图片描述
如果k == 2同理如下:
这里写图片描述
现在应该可以找到规律了,我们只需在当前位置往左上走,知道走到边界,然后再往上走即可
现在只需求和即可,我们利用 C n m = C n 1 m + C n 1 m 1 ,可以得到
a n s = C n + 1 k + n k
上式可以用数形结合来求出,这里请读者自行理解

这有个坑点,如果你用朴素的Lucas,会TLE,因为case特别多,我们需要做个预处理,题目上给了p给素数,范围只有1w,我们只需打出表即可,因为1e8的数组太大,我们需要把素数离散化一下,然后就可以了

参考代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e4 + 7;

typedef long long ll;

ll f[maxn][1534];
bool isp[maxn];
ll p[maxn];
int len;
int mm[maxn];
ll P;
int idx;
#define mod(x) ((x) % P)

void init() {
    isp[0] = isp[1] = true;
    for (int i = 2; i < maxn; i++) {
        if (!isp[i]) {
            p[len++] = i;
            for (int j = i + i; j < maxn; j += i) {
                isp[j] = true;
            }
        }
    }
    for (int i = 0; i < len; i++) mm[p[i]] = i;
    for (int i = 0; i < len; i++) {
        for (int j = 0; j < maxn; j++) {
            if(j == 0) f[j][i] = 1;
            else f[j][i] = (f[j - 1][i] * 1ll * j) % p[i];
        }
    }
}

ll qpow(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res = mod(res * 1ll * a);
        a = mod(a * 1ll * a);
        b >>= 1;
    }
    return res;
}

ll C(ll n, ll m) {
    if (n < m) return 0;
    return mod(mod(f[n][idx] * qpow(f[m][idx], P - 2)) * 1ll*  qpow(f[n - m][idx], P - 2));
}

ll lucas(ll n, ll m) {
    if(m == 0) return 1;
    return mod(lucas(n / P, m / P) * 1ll * C(n % P, m % P));
}

int main() {
    ll n,m;
    int cnt = 1;
    init();
    while (~scanf("%lld%lld%lld", &n, &m, &P)) {
        idx = mm[P];
        if (m > n / 2) m = n - m;
        printf("Case #%d: %lld\n",cnt++, mod(lucas(n + 1,m) + n - m));
    }
    return 0;
}

[3] ZOJ - 3557 How Many Sets II

题意: 给你一个集合里面是{1,2,3···,n} 让你取m个元素,使得彼此不相邻,求方案数

分析: 这是经典的隔板法,首先最后我们肯定是取出m个元素,剩下n - m个元素,这n-m个元素有
n - m +1 个空,然后我们在尝试这放着m个元素,这样答案就是
a n s = C n m + 1 m
利用lucas计算即可

参考代码

#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int maxn = 1e5 + 10;
typedef long long ll;
ll p;
#define mod(x) ((x) % p)

ll qpow(ll a, ll b) {
    ll res = 1;
    while (b) {
        if(b & 1) res = mod(res * 1ll * a);
        a = mod(a * 1ll * a);
        b >>= 1;
    }
    return res;
}

ll c(ll n, ll m) {
    ll up = 1, down = 1;
    for (ll i = 1; i <= m; i++) down = mod(down * 1ll * i);
    for (ll i = n - m + 1; i <= n; i++) up = mod(up *1ll * i);
    return mod(up *qpow(down, p - 2));
}

ll lucas(ll n, ll m) {
    if (m == 0) return 1;
    return mod(lucas(n / p, m / p) * c(n % p, m % p));

}

int main() {
    ll n,m;
    while (~scanf("%lld%lld%lld", &n, &m, &p)) {
        printf("%lld\n", lucas(n - m + 1, m));
    }
    return 0;
}

[4] HDU - 4349 Xiao Ming’s Hope

题意: 问你{ C n 0 , C n 1 , · · · · C n n 1 , C n n } 中有多少奇数

分析: 这里参考acdream的做法,甚是巧妙,
我们假设每个组合数都要对2取模,就变成了Lucas了,也是对n,m转化成了二进制后,我们发现
C 0 0 = 1 ··· C 0 1 = 0 ··· C 1 0 = 1 ···· C 1 1 = 1
我们将n转化成二进制以后,如果要是得 C n m % == 0
n某一位上是0时,所对应的m只能为0
而当n某一位上是1时,所对应的m可以为0 也可以为1
所以我们只需统计n转化成二进制后1的个数即可

附 : 这里用到了__builtin,相关位运算,参考这里

参考代码

#include <bits/stdc++.h>
using namespace std;
int main () {
    int n;
    while (~scanf("%d", &n)) {
        printf("%d\n", 1 << (__builtin_popcount(n)));
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/nobleman__/article/details/80020790
今日推荐