[FJOI2016]建筑师

1464677-20190312213407971-1477362712.png


题解

首先可以发现\(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 ;
}

转载于:https://www.cnblogs.com/beretty/p/10519892.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值