自己写一遍插板法的东西,顺便补上 oiwiki 上没有的一个证明。
偏向整理,别人估计不知道我在干什么。
基本模型:\(x_0+x_1+...+x_k=n\) 的正整数或非负整数解数量,可以抽象为元素组的划分。
正整数解的数量
也是插板法最基本的模型。
有 \(n\) 个相同的元素,将它们分成 \(k\) 组,并且要求每组不是空的,求统共有多少种分配方法。
注意是元素相同组别不同。
为什么标粗相同的元素?不相同就不能用插班法了,插班法依赖于元素的不可区分性。
我们将这个问题视作把 \(k-1\) 个隔板放入 \(n-1\) 个空隙之间。
也就是 \(\binom{n-1}{k-1}\)。
问题等同于求解 \(x_0+x_1+...+x_k=n\) 的正整数解数量。
这里的 \(x_i\) 表示 \(i\) 组的个数。
非负整数解的数量
现在去掉限制每组不是空,也就是允许每组是空的。
由于元素的等同的,我们可以先添加 \(k\) 个元素,再进行上述插班法,最后再抽去我们添加的元素。
得到答案 \(\binom{n+k-1}{k-1}\)=\(\binom{n+k-1}{n}\)。
问题等同于求解 \(x_0+x_1+...+x_k=n\) 的非负整数解数量。
以上便是基本的两大模型,我们遇到问题可以试着转化模型。
下界不同的整数解的数量
我们规定对于第 \(i\) 组必须至少要分配到 \(a_i\) 个元素。
这个时候我们选择转化成数学模型,问题变复杂时我们不能总是依赖直观。
我们设 \(x_i\) 为每组的个数,尝试转化成我们熟悉的数学模型。
\(x_0+x_1+...+x_k=n\)
设 \(x_i'=x_i-a_i\),这个样子我们就可以转化成以下东西。
\(x_0'+x_1'+...+x_k'=n - \sum a_i\)
其中 \(x_i\) 必须是非负整数。
这不就转化成之前的问题了。
答案自然是 \(\binom{n-\sum a_i +k-1}{n-\sum a_i}\)。
不相邻的排列数量
有 \(1到n\) 的自然数可以选(一个排列),我们需要选择 \(k\) 个数,使得这些数在大小上互相不相邻,请问有多少种选法。
这个看似不太可以使用插板法分析,明明元素之间并不是相同的。
但是我们发现两个离得最近的被选择的数之间的大小差异是相同的。
我们考虑如下转化。
设 \(x_i\) 为比 \(x\) 小的最大数与 \(x\) 中间的没有被选的数量,\(miao\) 表示比 \(k\) 大的没有被选的数量。
这个样子我们可以列出来式子 \(x_1+x_2+...+x_k+miao=n-k\)。
其中 \(x_1 \ge 0\),其他 \(x_i \ge 1\),\(miao\ge 0\)。
这个样子就成了之前的老问题。
我们转化一下,仿照上边的思路。
\(x_1'=x_1+1\),\(miao'=miao+1\)。
这样就成了第二个问题。
\(x_1'+x_2+...+x_k+miao'=n-k+2\)
\(x_i,miao\) 都是非负整数。
直接套刚才式子得出答案是 \(\binom{n-k+1}{k}\)
为什么不是 \(k-1\)?\(miao\) 也是元素啊。
简单的例题 P2638
复习玩排列组合正好写到一道,遂补上去。
一共有 \(n\) 个储存区,储存区里边可以放任意多 0 和 1,当然,也可以什么都不放。
我们的 0 和 1 的总数量是有限的,放的时候并不一定要放完才行。
0 一共有 \(a\) 个,1 一共有 \(b\) 个。
询问一共有多少种方法。
我一开始并没有思路,于是尝试将问题转化为数学模型来分析。
一转化出来就知道怎么做了。
设 \(x_i\) 表示 \(i\) 格子中的 0 个数
设 \(y_i\) 表示 \(i\) 格子中的 1 个数。
枚举使用了 \(i\) 个 0,\(j\) 个 1。
我们得到数学问题,分别求以下的非负整数解个数。
\(x_1+...+x_n=i\)
\(y_1+...+y_n=j\)
因为两个互不影响,所以把答案乘起来就行了,怎么算我就不多说了,和上边显然是一样的。
形式化就是求解 \(\sum_{i=0}^{a}\sum_{j=0}^{b}\binom{i+n-1}{i}\times\binom{j+n-1}{j}\)
有个神经病的 hack 需要 __int128 才可以通过。
代码↓
#include <bits/stdc++.h>
#define int __int128
using namespace std;
const int MN=51;
int C[MN][MN], ans=0;
void Pre(){for(int i=0; i<MN; ++i){for(int j=0; j<=i; ++j){if(!j) C[i][j]=1;else C[i][j]=C[i-1][j]+C[i-1][j-1];}}
}
int n, a, b;
inline void Read(int &n){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}n=x*f;
}
inline void print(int n){if(n<0){putchar('-');n*=-1;}if(n>9) print(n/10);putchar(n%10+'0');
}
signed main(){ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);Pre(); Read(n), Read(a), Read(b);for(int i=0; i<=a; ++i){for(int j=0; j<=b; ++j){ans+=C[i+n-1][i]*C[j+n-1][j];}}print(ans);return 0;
}