浅析排列组合、斯特林数、贝尔数、二项式定理与推论及其反演、子集反演、广义容斥
浅析排列组合、斯特林数、贝尔数、二项式定理与推论及其反演、子集反演、广义容斥
更好的阅读体验戳此进入
写在前面
内容较多,不保证正确性,不保证证明一定严谨,欢迎纠错。
题目编号可能并未顺序编号,仅按照做题顺序编号。
排列数
定义显然,从
有:
证明:感性证明即可,第一个位置可以选择
组合数
定义显然,从
有:
证明从意义上想即可,对于排列数
8种球盒问题(插板法)
这是一类十分经典的问题。首先为什么一共有八种呢,即我们考虑,球是否全部相同或不同(部分相同就是类似多重集相关问题了,后文有写),盒子之间是否完全相同,盒子是否允许空。不难发现共
同时我们规定球的数量为
球完全相同 盒子完全不同 不允许空盒
最经典的问题,我们考虑在
球完全相同 盒子完全不同 允许空盒
考虑额外添加
球完全不同 盒子完全不同 允许空盒
每个球都可能被放进
球完全不同 盒子完全相同 不允许空盒
第二类斯特林数,类似集合划分,但是限制必须划分为
考虑令
有转移:
即考虑第
其为第二类斯特林数,记作
则答案为:
球完全不同 盒子完全相同 允许空盒
答案较为显然,类似前者,为:
意义上理解一下即可,这个东西是贝尔数。其对应的是可以划分为空集的集合划分,或者说划分为
球完全不同 盒子完全不同 不允许空盒
在斯特林数的基础上乘上盒子之间的排列即可。
球完全相同 盒子完全相同 允许空盒
令
转移显然:
即考虑若我们盒子的数量多于球的数量,那么球的放置方式都是本质相同的,直接转移即可。反之则考虑第
则答案为:
值得注意的是,该状态也可以认为是将整数
球完全相同 盒子完全相同 不允许空盒
类比于前者,答案为:
即考虑先在每个盒子里放
斯特林数
斯特林数(Stirling Number)共有两类,称作第一类斯特林数和第二类斯特林数。
第二类斯特林数
我们先从第二类开始,因其会更常见常用很多。
第二类斯特林数,也叫斯特林子集数,可以记作
边界为
一般的递推式上文提到过,即:
当然这样的复杂度我们肯定是不能接受的,于是考虑通向式:
考虑证明:
令
显然有:
由二项式定理有:
当然此时我们不难想到,这并不是第二类斯特林数,因为第二类斯特林数要求集合之间是完全相同的,即不区分顺序,所以我们需要在
同行同列等如何求以及第一类斯特林数及其求法等之后补完多项式和生成函数再回来补。
多重集的排列组合数
多重集的排列
全排列较为简单,考虑
则显然多重集全排列数为从总排列数去除每个元素本身造成的重复排列的贡献,用乘法原理合起来即可,最终答案为:
而若是多重集的
多重集的组合
考虑若选择的
而若其大于了,那么我们就需要考虑到
若我们令不满足第
对于后者,不难想到对于
然后取并集的过程不难想到就是一个显然的容斥:
我们可以尝试将答案记录的更优美显然一些,即:
其中
圆排列
这个没什么可说的,就是去掉普通排列中的
亿些公式
(1)
从意义上或从公式上易证。
(2)
从公式上易证。
(3)
即杨辉三角。从组合意义上尝试证明:即从
(4)
考虑二项式定理:
(5)
考虑二项式定理中令
(6)
从意义上即可证明,从
(7)
令 (6) 中的
(8)
证明待补。
(9)
证明待补。
(10)
证明待补。
(11)
考虑从组合意义上证明:从
(12)
其中
证明待补。
反演
关于反演大概就是我们已知和式
二项式定理
二项式较为显然,即形如
二项式定理即考虑组合意义将上式展开:
证明:
考虑归纳法,对于
对于
我们考虑对下式乘上
于是此时下式
多项式定理
类比一下不难得到多项式定理:
其中:
不难发现这东西本质就是个多重集的排列。
广义二项式定理
二项式定理可推广到对于任意实数
这里简单介绍一下下降幂,即:
实数意义上的下降幂(我也不知道这玩意是不是存在)定义应该差不多,就是每次
然后对于实数的组合数,意义与一般的组合数相同,即:
这个东西的证明十分高妙,显然我不会,然后一种用处可以是去求指数函数值,在生成函数等里面还有很多重要用处,这里不再过多赘述。
二项式反演
首先写一下结论:
令:
那么:
也就是我们将
证明:
首先考虑感性证明:对于二项式反演,我们一般认为
所以说对于前者,即至多选
对于后者,我们可以认为对于恰好选
所以从意义上来讲这个一定是正确的。
考虑严谨证明:
不难发现可以将下式
令
套一下公式即可得到最终结果:
子集反演
这个东西实际上应该写在二项式反演之前,因为其比二项式反演更简单一些。
证明易证,考虑一般容斥原理即可。
例题 #1
求将
一般的 线性DP 显然,即:
然后这种简单递推式是可以通过特征方程等转成通向式的,不过这里我们考虑一些二项式反演的方法。
考虑令
然后再令
答案为
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template < typename T = int >
inline T read(void);
int N;
ll fact(int i){ll ret(1); for(int j = 1; j <= i; ++j)ret *= j; return ret;}
ll g(int i){return fact(N) / fact(i);}
ll ans(0);
int main(){
N = read();
for(int i = 0, mul(1); i <= N; ++i, mul *= -1)ans += g(i) * mul;
printf("%lld\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
例题 #2
一个有
考虑令
令
但是需要注意,直接这样算显然无法计算,于是考虑扩展欧拉定理,即若
本题模数为
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD (ll)(1e9 + 7)
template < typename T = int >
inline T read(void);
ll qpow(ll a, ll b, ll mod = MOD){
ll ret(1), mul(a);
while(b){
if(b & 1)ret = ret * mul % mod;
b >>= 1;
mul = mul * mul % mod;
}return ret;
}
int N, K;
ll ans(0);
ll fact[1100000], inv[1100000];
void Init(void){fact[0] = 1;
for(int i = 1; i <= 1010000; ++i)fact[i] = fact[i - 1] * i % MOD;
inv[1010000] = qpow(fact[1010000], MOD - 2);
for(int i = 1009999; i >= 0; --i)inv[i] = inv[i + 1] * (i + 1) % MOD;
}
ll C(int n, int m){if(n < m || n < 0 || m < 0)return 0; return fact[n] * inv[m] % MOD * inv[n - m] % MOD;}
ll F(ll i){return (qpow(2, qpow(2, N - i, MOD - 1)) - 1 + MOD) % MOD * C(N, i) % MOD;}
int main(){
Init();
N = read(), K = read();
for(int i = K, mul(1); i <= N; ++i, mul *= -1)(ans += mul * C(i, K) * F(i) % MOD) %= MOD;
printf("%lld\n", (ans + MOD) % MOD);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
例题 #3
有
不难想到令
总方案数即为乘起来,然后再乘一个枚举删除的
令
答案即为
复杂度是
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD (ll)(1e9 + 7)
template < typename T = int >
inline T read(void);
ll N, M;
ll fact[2100], inv[2100];
ll c[1100];
ll C(int n, int m){if(n < m || n < 0 || m < 0)return 0; return fact[n] * inv[m] % MOD * inv[n - m] % MOD;}
ll F(int x){
ll ret(1);
for(int i = 1; i <= M; ++i)(ret *= C(N - x - 1 + c[i], N - x - 1)) %= MOD;
ll mul = C(N, x);
return ret * mul % MOD;
}
ll qpow(ll a, ll b){
ll ret(1), mul(a);
while(b){
if(b & 1)ret = ret * mul % MOD;
b >>= 1;
mul = mul * mul % MOD;
}return ret;
}
void Init(void){fact[0] = 1;
for(int i = 1; i <= 2010; ++i)fact[i] = fact[i - 1] * i % MOD;
inv[2010] = qpow(fact[2010], MOD - 2);
for(int i = 2009; i >= 0; --i)inv[i] = inv[i + 1] * (i + 1) % MOD;
}
int main(){
Init();
N = read(), M = read();
for(int i = 1; i <= M; ++i)c[i] = read();
ll ans(0);
for(int i = 0, mul(1); i <= N - 1; ++i, mul *= -1)(ans += mul * F(i)) %= MOD;
printf("%lld\n", (ans + MOD) % MOD);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
例题 #4
存在
看到恰好就不难想到二项式反演了。
令
从意义上即考虑枚举是哪
同时还存在
于是令
然后需要注意求的是
大致的思路就是将求和转换一下变成类似前缀和,然后将与
令
显然可以对每个
预处理后复杂度为:
注意答案可能存在复数,需要对答案再次转正并取模。
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD (998244353ll)
template < typename T = int >
inline T read(void);
int N;
ll ans[1100];
ll fact[2100], inv[2100], pow2[2100];
// ll C(int n, int m){if(n < m || n < 0 || m < 0)return 0; return fact[n] * inv[m] % MOD * inv[n - m] % MOD;}
// ll F(int k){
// return pow2[k] * C(N, k) % MOD * C(N, k) % MOD * fact[2 * N - 2 * k] % MOD;
// }
ll qpow(ll a, ll b){
ll ret(1), mul(a);
while(b){
if(b & 1)ret = ret * mul % MOD;
b >>= 1;
mul = mul * mul % MOD;
}return ret;
}
void Init(void){fact[0] = pow2[0] = 1;
for(int i = 1; i <= 2010; ++i)fact[i] = fact[i - 1] * i % MOD;
inv[2010] = qpow(fact[2010], MOD - 2);
for(int i = 2009; i >= 0; --i)inv[i] = inv[i + 1] * (i + 1) % MOD;
for(int i = 1; i <= 2010; ++i)pow2[i] = pow2[i - 1] * 2 % MOD;
}
int main(){
// freopen("out.txt", "w", stdout);
Init();
for(int d = 0; d <= 1010; ++d)
for(int j = 0, mul(1); j <= d; ++j, mul *= -1)
(ans[d] += mul * pow2[j] % MOD * fact[2 * (d - j)] % MOD * inv[j] % MOD * inv[d - j] % MOD * inv[d - j] % MOD) %= MOD;
int T = read();
while(T--){
int N = read();
for(int i = 0; i <= N; ++i){
int d = N - i;
printf("%lld\n", ((ans[d] * pow2[i] % MOD * fact[N] % MOD * fact[N] % MOD * inv[i] % MOD) + MOD) % MOD);
}
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
例题 #5
存在
这道题虽然有着 “恰好” 在,但是本质上其并非二项式反演,或者说二项式反演推一半就会发现已经有答案了。
首先存在性质,即要么每一行都有一个车,要么每一列都有一个车。以行为例,若存在一行没有车,那么这一行显然都空着,如果想要不在这一行放任何车并覆盖这一行就只能在非本行的每一列放一辆车,即又符合后者了,
于是可以推导下一个性质,即
此时我们可以考虑钦定每行都有一个车,然后考虑其所在列的位置,对于每一列都有车的情况显然与前者严格对称,故
此时不难发现,若共有
这里我们自然可以通过经典套路讨论钦定
枚举哪
当然特别地,对于
考虑用二项式反演优化求第二类斯特林数的式子即可,最终复杂度 $ O(n \log n)
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD (998244353ll)
template < typename T = int >
inline T read(void);
ll N, K;
ll fact[210000], inv[210000];
ll C(int n, int m){if(n < m || n < 0 || m < 0)return 0; return fact[n] * inv[m] % MOD * inv[n - m] % MOD;}
ll qpow(ll a, ll b){
ll ret(1), mul(a);
while(b){
if(b & 1)ret = ret * mul % MOD;
b >>= 1;
mul = mul * mul % MOD;
}return ret;
}
void Init(void){fact[0] = 1;
for(int i = 1; i <= 201000; ++i)fact[i] = fact[i - 1] * i % MOD;
inv[201000] = qpow(fact[201000], MOD - 2);
for(int i = 200999; i >= 0; --i)inv[i] = inv[i + 1] * (i + 1) % MOD;
}
ll Sn(int m){
ll ret(0);
for(int i = 0; i <= m; ++i)(ret += ((m - i) & 1 ? -1 : 1) * qpow(i, N) % MOD * inv[i] % MOD * inv[m - i] % MOD) %= MOD;
return (ret + MOD) % MOD;
}
int main(){
Init();
N = read(), K = read < ll >();
if(K >= N)printf("0\n"), exit(0);
if(!K)printf("%lld\n", fact[N]), exit(0);
printf("%lld\n", 2ll * C(N, N - K) % MOD * fact[N - K] % MOD * Sn(N - K) % MOD);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
例题 #6
给定两个序列
显然我们需要先将序列
显然总对数为
令
对于新的一个数,显然其可以从
然后现在我们还需要讨论对于
令
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD (ll)(1e9 + 9)
template < typename T = int >
inline T read(void);
int N, K;
int A[2100], B[2100];
int cnt[2100];
ll ans(0);
ll dp[2100][2100];
ll fact[2100], inv[2100];
ll C(int n, int m){if(n < m || n < 0 || m < 0)return 0; return fact[n] * inv[m] % MOD * inv[n - m] % MOD;}
ll F(int i){return fact[N - i] * dp[N][i] % MOD;}
ll qpow(ll a, ll b){
ll ret(1), mul(a);
while(b){
if(b & 1)ret = ret * mul % MOD;
b >>= 1;
mul = mul * mul % MOD;
}return ret;
}
void Init(void){fact[0] = 1;
for(int i = 1; i <= 2010; ++i)fact[i] = fact[i - 1] * i % MOD;
inv[2010] = qpow(fact[2010], MOD - 2);
for(int i = 2009; i >= 0; --i)inv[i] = inv[i + 1] * (i + 1) % MOD;
}
int main(){
Init();
N = read(), K = read();
if((N + K) & 1)printf("0\n"), exit(0);
for(int i = 1; i <= N; ++i)A[i] = read();
for(int i = 1; i <= N; ++i)B[i] = read();
sort(A + 1, A + N + 1), sort(B + 1, B + N + 1);
for(int a = 1, b = 0; a <= N; ++a){
while(b + 1 <= N && B[b + 1] < A[a])++b;
cnt[a] = b;
}dp[0][0] = 1;
for(int i = 1; i <= N; ++i)for(int j = 0; j <= i; ++j)
dp[i][j] = (dp[i - 1][j] + (j ? (cnt[i] - (j - 1)) * dp[i - 1][j - 1] % MOD : 0)) % MOD;
int lim = (N + K) >> 1;
for(int j = lim, mul(1); j <= N; ++j, mul *= -1)
(ans += mul * C(j, lim) % MOD * F(j) % MOD) %= MOD;
printf("%lld\n", (ans + MOD) % MOD);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
例题 #7
LG-P6478 [NOI Online #2 提高组] 游戏。
//TODO
例题 #8
CF1228E Another Filling the Grid。
//TODO
例题 #9
//TODo
例题 #10
//TODO
例题 #11
//TODO
广义容斥
//TODO
UPD
update-2023_01_13 初稿 添加排列组合数 8种球盒问题 第二类斯特林数简介 多重集的排列组合 圆排列 排列组合相关公式 二项式定理推论及反演 多项式定理 广义二项式定理
update-2023_01_16 添加子集反演 例题 #1 #2 #3 #4 #5 #6
本文作者:tsawke
本文链接:https://www.cnblogs.com/tsawke/p/17180268.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步