声明:博主是个菜比,如果哪里写错了,请留言说明,谢谢,部分参考acdreamers大牛
定义 :
推导
这个自行百度吧,和二项式有关,下面看下结论吧(我也不懂)
结论
已知:
逆元可以使用费马大定理或者扩展欧几里得都行
经典例题:
[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
个豆子来当做板子来分割,方案为
同理,我们把若干子问题这样求出,答案依次为:
上式是由
推得
然后就利用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?
题意: 已知在杨辉三角中,给你一个位置(简称
),问你从(0,0)
到(n,k)
经过的数字和最小,结果对P取模,每次可以往下走,或者往右下走。
分析 : 首先杨辉三角是左右对称的,如果所求的k > n / 2时,直接令k = n - k;即可,然后我们只考虑在一侧的情况即可,我们这样想如果k == 0
时,我们一直往下走即可,假设n = 5,如果k = 1时
,我们肯定优先往下走,然后到了n - 1的位置,再往右下走即可 简图如下 :
如果k == 2
同理如下:
现在应该可以找到规律了,我们只需在当前位置往左上走,知道走到边界,然后再往上走即可
现在只需求和即可,我们利用
,可以得到
上式可以用数形结合来求出,这里请读者自行理解
这有个坑点,如果你用朴素的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个元素,这样答案就是
利用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
题意: 问你{ } 中有多少奇数
分析: 这里参考acdream的做法,甚是巧妙,
我们假设每个组合数都要对2
取模,就变成了Lucas了,也是对n,m
转化成了二进制后,我们发现
···
···
····
我们将n
转化成二进制以后,如果要是得
,
当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;
}