基础数论(第二版)
Part 1.排列组合
排列
将
这是一个经典的排列问题,通常我们用
计算公式为
特别地,当
组合
在
这是一个经典的求组合数的问题,我们可以用
计算公式为
c[0][0]=1;
for(int i=1;i<=n;i++)c[i][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
}
}
Part 2.隔板法
有
对于上面那个问题,我们可以设这
一个很重要的变式
假设现在有一个点在数轴的原点上,它要走到点
这个问题我们暂且可以称它为乱窜的点,如果这个点一直向终点方向走那么只需要走
最后这个问题的答案为
Part 3.卢卡斯定理(lucas)
卢卡斯定理用于求解大组合数取模的问题,其中模数必须为质数。正常的组合数运算可以通过递推公式求解,但当问题规模很大,而模数是一个不大的质数的时候,就不能简单地通过递推求解来得到答案,需要用到卢卡斯定理。
卢卡斯定理的具体内容是对于所有的质数
特别地,当
代码实现:
long long Lucas(long long n, long long m, long long p) {
if (m == 0) return 1;
return (C(n % p, m % p, p) * Lucas(n / p, m / p, p)) % p;
}
Part 4.杨辉三角
杨辉三角又称帕斯卡三角,它的每一个数都是组合数,每一个数都等于上一行中这个位置左右两边的数之和,没有的就记为
这张图片中所呈现的就是杨辉三角。
杨辉三角的性质
- 杨辉三角中第
i 行j 列的数就是C(i,j) 。 - 杨辉三角有一定的对称性,即
C(i,j)=C(i,i-j) 。 - 杨辉三角的每一行对应于二项式展开的系数。例如
(a+b)^n 的展开式中各项的系数就是杨辉三角的第n 行。Part 5.斯特林数
第二类斯特林数
(先别急着问为什么先讲第二类)
第二类斯特林数表示讲
其递推表达式为:
代码如下:
f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j]=(f[i-1][j]*j+f[i-1][j-1])%mod;
}
}
第一类斯特林数
第一类斯特林数是在第二类斯特林数的基础上加了一个条件:每个子集除了非空之外轮换也算作一种方案,即
其递推表达式为:
代码如下:
s[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=i&&j<=m;j++){
s[i][j]=s[i-1][j-1]+(i-1)*s[i-1][j]%mod;
}
}
Part 6.例题
例题 1
题目描述
计算
解析
从题目描述中很容易就能看出是卢卡斯定理,但是这里让我们计算
参考代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,p;
int ksm(int x,int y,int mod){
int res=1;
while(y){
if(y&1ll)res=res*x%mod;
x=x*x%mod;
y>>=1ll;
}
return res;
}
int get_comb(int x,int y){
int a=1,b=1,c=1;
for(int i=1;i<=x;i++){
a=a*i%p;
if(i==y)b=a;
if(i==x-y)c=a;
}
b=ksm(b,p-2,p);
c=ksm(c,p-2,p);
return a*b%p*c%p;
}
int lucas(int x,int y){
if(x<y||x<0||y<0)return 0;
else if(x<p)return get_comb(x,y);
else return lucas(x%p,y%p)*lucas(x/p,y/p)%p;
}
signed main(){
cin>>n>>m>>p;
cout<<lucas(n,m);
return 0;
}
例题 2
题目描述
何老板打算在 NK 食堂办酒席,宴请信竞队队员。信竞队共有
然后由你来预订酒桌和安排队员们就座。何老板要求酒桌的数量不超过
解析
从题目不难看出是第二类斯特林数加动态规划,具体的解请看代码里的注释,这里就不再多说了。
参考代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1000000007;
int n,m,r,k,ans,f[1005][1005],dp[1005][1005];
//$dp_{i,j}$ 表示前 $i$ 个人中选 $j$ 个人的情况。
//$f_{i,j}$ 表示第一类斯特林数
signed main(){
cin>>n>>r>>k>>m;
for(int i=1;i<=n;i++){
dp[i][1]=i;//只选一个人有 $i$ 种情况
dp[i][0]=1;//一个人也不选也是一种情况
}//初始化
for(int i=k+1;i<=n;i++){
for(int j=1;j<=n;j++){
dp[i][j]=(dp[i-k][j-1]+dp[i-1][j])%mod;
}
}
f[0][0]=1;
for(int i=1;i<=r;i++){
for(int j=1;j<=m;j++){
f[i][j]=(f[i-1][j]*j%mod+f[i-1][j-1]%mod)%mod;
//左:选,右:不选
}
}
for(int j=1;j<=m;j++){
ans=ans+f[r][j];//$1$ 到 $m$ 的桌子情况数
ans=ans%mod;
}
cout<<ans*dp[n][r]%mod;
return 0;
}
完结撒花!
最后致敬那个天天请客的何老板。