[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 ;
}
posted @ 2019-03-12 21:46  beretty  阅读(103)  评论(0编辑  收藏  举报