P4609 [FJOI2016] 建筑师 题解
题意:长度为n的排列p,大数会把小数挡住,已知从左看能看到A个数,从右看能看到B个数,求有多少p符合。(n<=50000,A<=100,B<=100)
Solution:
斯特林数,这题比较良心,不需要nlogn求,可以用递推。
第一类斯特林数 \([^n_k]\) 是指 n 个数划分为 k 个圆排列的方案数。知乎繁凡:小学生都能看懂的三类斯特林数
上面这篇博客里提到一个了斯特林数的一个性质,和这题有关系:\(\sum\limits_{k=0}^n[^n_k]=n!\)
虽然博客里是用生成函数来证明的,不过倒是有另一种理解方式:
n 的阶乘容易想到排列 p 的所有方案,如果我们能把每一个排列一一对应到斯特林数上,就能说明两者相等。(这个思想类似于prufer序列与无根树的一一对应)
我们把 n 个数划分为 k 个圆排列的情况用以下步骤表示成序列:
①:找出每个圆排列中的最大值,将圆排列从此处断开。
②:每个圆排列都以最大值为首,将圆上数字展开平铺成序列。
③:将最大值排序,从小到大将所有圆排列平铺后的序列连接起来。
例如 n=7,k=3 的某个情况:{7,3} {5,1,4} {2,6},按照上述步骤,最终序列为 5 1 4 6 2 7 3,只有这样一种情况可以转化为这个序列,为了便于理解,我们再将序列转化为圆排列。5 1 4 6 2 7 3,从左边看可以看到三个连续上升的数 5 6 7,这代表了每个圆排列中的最大值,所以对应了 k=3 ,而序列中剩余的数字填写的位置一一对应了圆排列上其他数字的位置,即{5,1,4} {6,2} {7,3},这和开始时我们给出的 {7,3} {5,1,4} {2,6} 是相同的,因为每个圆排列内可以循环移动,且三个圆排列顺序无关。
因此 \(\sum\limits_{k=0}^n[^n_k]=n!\) 就可以理解了。
通过刚才的证明,我们也发现了斯特林数和这题的关系:n 个数,从左向右看能看到 k 个数字,方案数其实就是 \([^n_k]\)。
这题不仅有从左到右看的限制,还有从右到左看的限制,怎么做?我们转化下题意。
我们发现可以从最大的数字 n 分开,从左向右看只能看到 n 左边的数,从右向左只能看到 n 右边的数。我们把类似于刚才 {5,1,4} 这样同一个圆排列上的数字看成一组,则整个序列从中间分开后,左边的数字有 A-1 组,右边的数字有 B-1 组。我们可以把右边的 B-1 组按最大值排序插入到左边,这样左边的情况数就是 \(\left[\begin{matrix}n-1\\A+B-2\end{matrix}\right]\),把 n-1 个数分成 A+B-2 组。
在 A+B-2 组中,有 A-1 组在左边,B-1 组在右边,因此总方案数为 \(\left[\begin{matrix}n-1\\A+B-2\end{matrix}\right]\left(\begin{matrix}A+B-2\\A-1\end{matrix}\right)\)。
挺有趣的。
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define FOR() ll le=e[u].size();for(ll i=0;i<le;i++)
#define QWQ cout<<"QwQ\n";
#define ll long long
#include <vector>
#include <queue>
#include <map>
using namespace std;
const ll N=501010;
const ll qwq=303030;
const ll inf=0x3f3f3f3f;
const ll p=1000000007;
ll T;
ll n,A,B;
ll s[55555][222],c[222][222];
inline ll read() {
ll sum = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * ff;
}
int main() {
s[0][0] = 1;
for(ll i=1;i<=50000;i++)
for(ll j=1;j<=200;j++) s[i][j] = (s[i-1][j-1] + s[i-1][j] * (i-1) %p) %p;
for(ll i=0;i<=200;i++) c[i][0] = 1;
for(ll i=1;i<=200;i++)
for(ll j=1;j<=200;j++) c[i][j] = (c[i-1][j-1] + c[i-1][j]) %p;
T = read();
while(T--) {
n = read(); A = read(); B = read();
cout<<s[n-1][A+B-2]*c[A+B-2][A-1]%p<<"\n";
}
return 0;
}