[FJOI2016]建筑师
题解
首先可以发现\(n\)这个楼将序列分成的两半
前一半就是让你选出\(A\)个数,并且以\(n\)为结尾,每个数到下一个数的这段区间的数一定小于这个数
那么这可以看做是什么呢?
是不是可以把每段区间内的数看做是一个环然后做环排列?
最大的数就当做环首
这就是\((A-1)\)个环排列
那么这就是第一类斯特林数了
\(S(n,m)=(n-1)S(n-1,m)+S(n-1,m-1)\)表示对于新加入的数是放入以前的环中还是新开一个环
那么就枚举楼\(n\)在哪里,然后前后做环排列即可
这样单次询问复杂度是\(O(n)\)
可以发现把\(n\)拎出来之后就剩下了\(A+B-2\)个环排列,把\((n-1)\)个数排成\(A+B-2\)个环排列然后把任意\(A-1\)个弄到\(n\)的前面就是答案了
代码
#include<map>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
const int M = 50050 ;
const int mod = 1e9 + 7 ;
using namespace std ;
inline int read() {
char c = getchar() ; int x = 0 , w = 1 ;
while(c>'9'||c<'0') { if(c=='-') w = -1 ; c = getchar() ; }
while(c>='0'&&c<='9') { x = x*10+c-'0' ; c = getchar() ; }
return x*w ;
}
int n , A , B , ans ;
int fac[M] , inv[M] , finv[M] , s[M][205] ;
inline int C(int n , int m) {
return 1LL * fac[n] * finv[m] % mod * finv[n - m] % mod ;
}
int main() {
fac[0] = 1 ;
for(int i = 1 ; i <= 50000 ; i ++) fac[i] = 1LL * fac[i - 1] * i % mod ;
inv[1] = 1 ;
for(int i = 2 ; i <= 50000 ; i ++) inv[i] = 1LL * (mod - mod / i) * inv[mod % i] % mod ;
finv[0] = 1 ;
for(int i = 1 ; i <= 50000 ; i ++) finv[i] = 1LL * finv[i - 1] * inv[i] % mod ;
s[0][0] = 1 ;
for(int i = 1 ; i <= 50000 ; i ++)
for(int j = 1 ; j <= min(200 , i) ; j ++)
s[i][j] = (s[i - 1][j - 1] + 1LL * (i - 1) * s[i - 1][j] % mod) % mod ;
int Case = read() ;
while(Case --) {
n = read() ; A = read() ; B = read() ; ans = 0 ;
printf("%d\n",1LL * s[n - 1][A + B - 2] * C(A + B - 2 , A - 1) % mod) ;
}
return 0 ;
}