斯特林数
斯特林数一共分为两类,较第一类来说,第二类斯特林数更加常用,接下来分别介绍他们。
第一类斯特林数:
定义:
用
递推式:
第一种,考虑对于前
第二种,考虑前
综上,第一类斯特林数的递推式为:
边界条件为
应用:
说实话遇到的需要用第一类斯特林数来解决的题目其实并不是很多……这里还是放几道题来分享学习下。
1.[HDU 3625] Examining the Rooms
酒店里发生了一起谋杀案。作为镇上最好的侦探,您应该立即检查酒店的所有
个房间。然而,房间的所有门都被锁上了,钥匙只是锁在房间里,真是一个陷阱!您知道每个房间只有一个密钥,并且所有可能的分布都具有相等的可能性。例如,如果 ,则有 个可能的分布,每个分布的可能性为 。为了方便起见,我们将房间从 编号到 ,房间 的钥匙编号为钥匙 ,房间 的钥匙是钥匙 ,依此类推,
要检查所有房间,你必须用力摧毁一些门。但是你不想破坏太多,所以你采取以下策略:起初,你手里没有钥匙,所以你随机摧毁一扇锁着的门,进入房间,检查它,然后拿到里面的钥匙。然后,也许你可以用新钥匙打开另一个房间,检查它并得到第二把钥匙。重复此操作,直到您无法打开任何新房间。如果仍然有房间未检查,您必须随机选择另一扇未打开的门以强制销毁,然后重复上述过程,直到所有房间都检查完毕。
现在,你最多只能用武力摧毁扇门。更重要的是, 号房间里有一个非常重要的人。你不被允许破坏 号房间的门,也就是说,检查 号房间的唯一方法是用相应的钥匙打开它。你想知道你最终能检查所有房间的概率。
分析:
炸开一扇门,拿走里面的钥匙,开启另一扇门,再拿走这个房间里面的钥匙,再打开一扇门……如此循环下去,直到打开的房间里面的钥匙对应的是已经打开过的房间的钥匙。这个过程其实就了一个圆排列。对于
因为不能使第一个房间单独成为一个圆排列,因此还要将这种情况减去,最终答案为:
Code:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 20;
long long s[MAXN + 5][MAXN + 5],n;
int fact(int a){
int ret = 1;
while(a){
ret = (long long)a * ret;
a--;
}
return ret;
}
int t,k;
signed main(){
s[0][0] = 1;
for(int i = 1; i <= MAXN; i++){
s[i][i] = 1;
s[i][1] = fact(i - 1);
}
for(int i = 1; i <= MAXN; i++){
for(int j = 2; j < i; j++){
s[i][j] = (s[i - 1][j - 1] + (long long)(i - 1) * s[i - 1][j]);;
//cout << s[i][j] << "\n";
}
}
cin >> t;
while(t--){
cin >> n >> k;
int ans = 0;
for(int i = 1; i <= k; i++){
ans += s[n][i] - s[n - 1][i - 1];
}
double an = 1.0 * ans / fact(n);
printf("%.4lf\n",an);
}
return 0;
}
2.HDU 4372 Count the Buildings
城市中有
座建筑物直线站立,编号从 到 。所有建筑物的高度都是不同的,介于 和 之间。当你站在第一栋建筑前面向前看时,你可以看到 栋,当你站在最后一栋建筑后面向后看时,你可以看到 栋建筑。如果建筑物比您与其之间的任何建筑物都高,则可以看到建筑物。
现在,给定, , ,你的任务是弄清楚所有建筑物可以有多少种方式。
无论从那个方向看,最高的楼都是会被看见的。因此问题变为将剩下的
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e3;
const int MOD = 1e9 + 7;
int f[MAXN + 5],inv[MAXN + 5],s[MAXN + 5][MAXN + 5],n;
int qpow(int a,int n){
int ret = 1;
while(n){
if(n & 1){
ret = (long long)ret * a % MOD;
}
n /= 2;
a = (long long)a * a % MOD;
}
return ret;
}
void init(){
f[0] = 1;
for(int i = 1;i <= MAXN; i++){
f[i] = (long long)f[i - 1] * i % MOD;
}
inv[MAXN] = qpow(f[MAXN],MOD - 2);
for(int i = MAXN; i >= 1; i--){
inv[i - 1] = (long long)inv[i] * i % MOD;
}
}
int c(int a,int b){
return (long long)f[a] * inv[b] % MOD * inv[a - b] % MOD;;
}
int t,ff,b;
int main(){
cin >> t;
s[0][0] = 1;
init();
for(int i = 1; i <= MAXN; i++){
s[i][i] = 1;
s[i][1] = f[i - 1];
}
for(int i = 1; i <= MAXN; i++){
for(int j = 2; j < i; j++){
s[i][j] = (s[i - 1][j - 1] + (long long)(i - 1) * s[i - 1][j]) % MOD;;
//cout << s[i][j] << "\n";
}
}
while(t--){
cin >> n >> ff >> b;
if(ff + b - 1 > n || max(ff,b) <= 1) {
printf("0\n");
continue;
}
cout << (long long)s[n - 1][b + ff - 2] * c(b + ff - 2,b - 1) % MOD << "\n";;
}
return 0;
}
第二类斯特林数:
定义:令 表示将 个元素划分成 个集合的方案数。
递推式:
仍按照递推第一类斯特林数的方法进行思考。如果前
边界条件为
第二类斯特林数还有一个重要的通项公式:
至于怎么得出的,我也不知道。知道的可以在评论区里分享一下。
习题:
1.CF1342E Placing Rooks
有这样一个问题:
在的国际象棋棋盘上放 个车,要求满足两个条件:
所有的空格子都能被至少一个车攻击到。
恰好有对车可以互相攻击到。
答案对取模。
分析:
好题。
保证每一个格子都要被攻击到,换句话说,就是要每一行都有一个棋子,这就大大简化了问题了。
一个重要的结论:如果有
那么,只是求
综上,答案为:
假如当前方案并不只是一列,那么还可以将其旋转一次得到新的方案,此时就应该将答案乘
Code:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5;
const int MOD = 998244353;
int inv[MAXN + 5],f[MAXN + 5];
int qpow(int a,int n){
int ret = 1;
while(n){
if(n & 1){
ret = (long long)ret * a % MOD;
}
n /= 2;
a = (long long)a * a % MOD;
}
return ret;
}
void init(){
f[0] = 1;
for(int i = 1; i <= MAXN; i++){
f[i] = (long long)f[i - 1] * i % MOD;
//cout << f[i] << "\n";
}
inv[MAXN] = qpow(f[MAXN],MOD - 2);
for(int i = MAXN; i > 0; i--){
inv[i - 1] = (long long)inv[i] * i % MOD;
}
}
int c(int a,int b){
return (long long)f[a] * inv[b] % MOD * inv[a - b] % MOD;;
}
int s(int n,int m){
long long ans = 0;
for(int i = 0; i <= m; i++){
int t = (i % 2 == 0) ? 1 : -1;
ans = (1ll * ans % MOD + t * ((long long)c(m,i) % MOD * qpow(m - i,n) % MOD)) % MOD;
ans %= MOD;
// cout << c(m,i) << " " << qpow(m - i,n) << " " << c(m,i) * qpow(m - i,n) % MOD << " " << ans << "\n";
}
ans = 1ll * ans * inv[m] % MOD;
return ans;
}
signed main(){
init();
int a,b;
cin >> a >> b;
if(a - 1 < b){
cout << "0";
return 0;
}
cout << (((b == 0) ? 1 : 2) * (long long)s(a,a - b) * c(a,a - b) % MOD * f[a - b] % MOD + MOD) % MOD;;
}
2.第二类斯特林数的奇偶性分析:
给定
,求
(借鉴了大佬@LuckyGlass的思想)
考虑对
当
当
注意到,这两种转移在图上是这样表现的:
可以看出,这个问题就相当于求点
再细致观察,图中的点大体上分为十字交叉的蓝点和一条直线上的黄点两种点。这两种点分别对应
容易证明,从
- 从
移动到 - 从
移动到
当
并且,除了第一步以外,所有第一种移动方式都是成对出现的,因为你会移动到
所以,你采用的移动方式必然为:第一种移动方式,若干次第二种移动方式,第一种移动方式,第一种移动方式,若干次第二种移动方式…………第一种移动方式。
由于第一种移动方式是两两一组的,那么在它们之间的间隔,可以放零到若干次第二种移动方式。这样的间隔一共有
令
接下来,我们只需要判断组合数的奇偶性即可。这里需要用到一个结论,即如果
Code:
点击查看代码
#include<iostream>
using namespace std;
int t,m,n;
int main(){
cin >> t;
while(t--){
cin >> n >> m;
if((m & 1) == 0){
m--,n--;
}
n = n - m / 2;
m = (m + 1) / 2;
n--,m--;
if((n & m) == m)cout << "1\n";
else cout << "0\n";
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)