第一行照例本鸽子精咕咕咕。
以下为正文:
一、基础计算
1.基础计算指的是给定一个n,k计算,一般来说直接利用公式计算即可
。
2.但这种情况一般会涉及到爆long long的问题,需要进行mod,众所周知,除法不能进行模运算,所以可用
①乘法逆元来做(逆元的博客我还没写,写了再放上来)
②另一个公式,利用数组递推求得,过程中不停取模。
类似于这道题:SDNU1311,我用②做的。
下附递推公式:
for(int i = 0; i <= 1000; ++i)
{
for(int j = 0; j <= i; ++j)
{
if(i == j)
dp[i][j] = 1;
else
dp[i][j] = (dp[i-1][j-1]+dp[i-1][j])%mod;
}
}
其实还有很多其他公式,由ryc师哥提供一个文件,我放到网盘里了,永久有效,欢迎自行下载。提取码 77vd
其他:其实做了一段时间水题后,过了刚入坑的萌新阶段后,你会发现基础计算其实少的可怜,所以就涉及到下面的知识了。
二、第一类斯特林数
1.多用于求p个不同人围k个相同圆桌而坐,要求各桌非空,其不同方案数(环形)
2.①首先我们想到,当第p个人要坐时,有两种可能。
② 前p-1个人坐了k-1个桌,自己一个人坐一桌;前p-1个人坐了k桌,他随便坐。(这里需要注意,随便坐是指随便某桌的某人边上,即直接考虑他坐在哪个人旁边即可)。
③可得公式dp[n][k] = dp[n-1][k-1]+(n-1)dp[n-1][k],这里乘n-1的原因如括号内解释。
注:其实第一类斯特林数还有好多应用,但我不会(理直气壮),留个坑。
练习题目:HDUOJ4372
思路:
①首先我们要知道:n的环排列的个数与n-1个元素的排列的个数相等。(当n个元素线性排列时为n!,但成环时会重复,ex:12345,54321为同一个环)
②根据题意,我们可以知道,往右往左看,最高的楼都是可以看到的,那么我们以最高楼为分界线,最高楼一组,左边分为f-1组,右边分为b-1组(左看f组,右看b组)。用每组最高元素代表并截取该组(其余元素任意排列),那么组与组中的最高元素一定是单调递增的,且最高元素一定在最左or最右,那么本组的元素线性排列,就相当于整体元素的全排列。
③所以,整体的结果就是先把n-1个元素分成f-1+b-1组(去掉最高元素),之后把组内元素进行环排列(也就是第一类斯特林),乘起来即为结果。
但实际上我还是不明白代码中公式的原因,我是辣鸡。留个坑。
#include <iostream>
#include <cstdio>
#include <algorithm>
#define MOD 1000000007
#define ll long long
using namespace std;
ll s[2005][2005] = {0};//strling
ll c[2005][2005] = {0};//组合数
void init()
{
for(int i = 1; i <= 2000; ++i)
s[i][0] = 0,c[i][0] = 1;
c[1][1] = 1;
for(int i = 2; i <= 2000; ++i)
for(int j = 1; j <= i; ++j)
{
c[i][j] = (c[i-1][j-1]+c[i-1][j])%MOD;
}
s[0][0] = 0;
s[1][1] = 1;
for(int i = 2; i <= 2000; ++i)
{
for(int j = 1; j <= i; ++j)
{
s[i][j] = (s[i-1][j-1]+(i-1)*s[i-1][j])%MOD;
}
}
}
int main()
{
init();
int t;
scanf("%d",&t);
while(t--)
{
int n;
int f,b;
ll ans;
scanf("%d%d%d",&n,&f,&b);
if(f+b-2 <= 2000)
ans = (c[f+b-2][f-1]*s[n-1][f+b-2])%MOD;
else
ans = 0%MOD;//防止re,WA了n次都因为这个
printf("%lld\n",ans%MOD);
}
return 0;
}
三、第二类斯特林数
1.多用于求n个有区别小球,要放进m个相同盒子里,且每个盒子非空的方案数
2.同样,最后一个球,有两种情况:前n-1个球放到了m-1个盒中它自己一个盒,或者前n-1个球放到了m个盒中,它随便放。
我觉得这个比第一类好理解,算了,觉得难理解大概因为我是个辣鸡
题目练习:SDNU1011
但是这个题要注意的是,因为题目中盒子是不同的,所以需要在最后将盒子进行全排列,所以为*
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define ll long long
using namespace std;
ll j(int x)
{
ll res = 1;
for(int i = 1; i <= x; ++i)
res *= i;
return res;
}
int main()
{
int n,r;
ll dp[11][11];
scanf("%d%d",&n,&r);
ll a = j(r);
for(int i = 0; i <= n; ++i)
{
for(int j = 0; j <= r; ++j)
{
if(i == 0)
{
dp[i][j] = 0;
}
else
{
if(j == 0 || i == 0 || i < j)
dp[i][j] = 0;
else if(j == 1 || i == j)
dp[i][j] = 1;
else
{
dp[i][j] = dp[i-1][j]*j+dp[i-1][j-1];
}
}
}
}
printf("%lld",dp[n][r]*a);
return 0;
}
现穿插博文无关回忆:
突然想起高二学排列组合的时候。
剑哥在讲台上用一张讲义讲了十几种情况方法,然而我日常上课睡觉划水啥都没学进去。后来学委从电教楼二楼穿过萃英广场,爬到教学楼四楼,花一个大课间的时间给我详细讲各种排列组合。
那时候她停课学数竞,每天都很忙,却还是愿意因为我随口的一句“你能不能教教我排列组合”,而来回好几次(有的大课间我出去不在,有的我在睡觉)(是的我成绩不好都是我自己作的)(但你能相信她甚至拿了张纸备课吗)(我当时特想让她把那张纸留下但没好意思开口)。
她真好啊,那时候也真好啊。
怀念后继续往前走。
欢迎指出错误qwq