题意

给你三个正整数 \(n,a,b\),定义 \(A\) 为一个排列中是前缀最大值的数的个数,定义 \(B\) 为一个排列中是后缀最大值的数的个数,求长度为 \(n\) 的排列中满足 \(A = a\) 且 \(B = b\) 的排列个数。\(n \le 10^5\),答案对 \(998244353\) 取模。

Sol

首先可以设一个 \(DP\) 状态 \(f(i,j)\) 表示,长度为 \(i\) 的排列,有 \(j\) 个前缀最大值的方案数。

那么转移就是枚举新放一个最小值,只有放在序列开头才有 \(1\) 的贡献:

\[f(i,j)=f(i-1,j-1)+(i-1)\times f(i-1,j)
\]

最后的答案就是枚举最大值 \(n\) 放在位置 \(i\),然后左边长度为 \(i-1\) 且有 \(a-1\) 个前缀最大值,右边长度为 \(n-1-i\) 且有 \(b-1\) 个后缀最大值,可以发现这个后缀最大值和前缀最大值的方案是相等的,那么最终的答案就是:

\[ans=\sum_{i=1}^n C(n-1,i-1)\cdot f(i-1,a-1)\cdot f(n-i-1,b-1)
\]

稍微熟练一点就可以看出,这个 \(f(i,j)\) 本质上就是第一类斯特林数,即 \(i\) 个数放 \(j\) 个圆排列的方案数。

所以这个式子就可以化简了,从组合意义上理解就是,从 \(n-1\) 个数拿出来形成 \(a+b-2\) 个圆排列,其中把 \(a-1\) 个放在 \(n\) 前面的方案数。最后答案就变成了:

\[ans=s(n-1,a+b-2)\times C(a+b-2,a-1)
\]

只需要预处理第一类斯特林数一行就行。分治\(\mathrm{NTT}\)复杂度\(O(n\log^2 n)\),倍增复杂度\(O(n\log n)\)。具体见这里

Code

两种都写了下

// 分治NTT
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using std::min;
using std::max;
using std::swap;
using std::vector;
typedef double db;
typedef long long ll;
#define pb(A) push_back(A)
#define vec std::vector<int>
#define pii std::pair<int,int>
#define all(A) A.begin(),A.end()
#define mp(A,B) std::make_pair(A,B)
const int N=4e5+5;
const int mod=998244353; int n,A,B,lim;
int a[N],b[N],rev[N]; int ksm(int a,int b=mod-2,int ans=1){
while(b){
if(b&1) ans=1ll*ans*a%mod;
a=1ll*a*a%mod;b>>=1;
} return ans;
} void ntt(int *f,int g){
for(int i=1;i<lim;i++) if(i<rev[i]) swap(f[i],f[rev[i]]);
for(int mid=1;mid<lim;mid<<=1){
int tmp=ksm(g,(mod-1)/(mid<<1));
for(int R=mid<<1,j=0;j<lim;j+=R){
for(int w=1,k=0;k<mid;k++,w=1ll*w*tmp%mod){
int x=f[j+k],y=1ll*w*f[j+k+mid]%mod;
f[j+k]=(x+y)%mod,f[j+k+mid]=(mod+x-y)%mod;
}
}
} if(g>3)
for(int in=ksm(lim),i=0;i<lim;i++) f[i]=1ll*f[i]*in%mod;
} vec mul(vec A,vec B,int n){
lim=1;while(lim<=n) lim<<=1;
for(int i=1;i<lim;i++) rev[i]=(rev[i>>1]>>1)|(i&1?lim>>1:0);
for(int i=0;i<lim;i++) a[i]=(i<A.size()?A[i]:0);
for(int i=0;i<lim;i++) b[i]=(i<B.size()?B[i]:0);
ntt(a,3),ntt(b,3);
for(int i=0;i<lim;i++) a[i]=1ll*a[i]*b[i]%mod;
ntt(a,(mod+1)/3); vec now;
for(int i=0;i<=n;i++) now.pb(a[i]);
return now;
} vec solve(int l,int r){
if(l==r){vec now;now.pb(l),now.pb(1);return now;}
int mid=l+r>>1; return mul(solve(l,mid),solve(mid+1,r),r-l+1);
} int S(int n,int m){
if(!n) return 1;
if(n<m) return 0;
vec now=solve(0,n-1);
return now[m];
} int getint(){
int X=0,w=0;char ch=getchar();
while(!isdigit(ch))w|=ch=='-',ch=getchar();
while( isdigit(ch))X=X*10+ch-48,ch=getchar();
if(w) return -X;return X;
} int C(int n,int m){
if(n<m or n<0 or m<0) return 0;
int now=1;
for(int i=1;i<=n;i++) now=1ll*now*i%mod;
for(int i=1;i<=m;i++) now=1ll*now*ksm(i)%mod;
for(int i=1;i<=n-m;i++) now=1ll*now*ksm(i)%mod;
return now;
} signed main(){
n=getint(),A=getint(),B=getint();
printf("%lld\n",1ll*S(n-1,A+B-2)*C(A+B-2,A-1)%mod);
return 0;
}
// 倍增
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using std::min;
using std::max;
using std::swap;
using std::vector;
typedef double db;
typedef long long ll;
#define pb(A) push_back(A)
#define pii std::pair<int,int>
#define all(A) A.begin(),A.end()
#define mp(A,B) std::make_pair(A,B)
const int N=4e5+5;
const int mod=998244353; int lim,rev[N],pw[N];
int a[N],b[N],c[N],d[N];
int n,A,B,fac[N],ifac[N]; int getint(){
int X=0,w=0;char ch=getchar();
while(!isdigit(ch))w|=ch=='-',ch=getchar();
while( isdigit(ch))X=X*10+ch-48,ch=getchar();
if(w) return -X;return X;
} int ksm(int a,int b=mod-2,int ans=1){
while(b){
if(b&1) ans=1ll*ans*a%mod;
a=1ll*a*a%mod;b>>=1;
} return ans;
} void ntt(int *f,int g){
for(int i=1;i<lim;i++) if(i<rev[i]) swap(f[i],f[rev[i]]);
for(int mid=1;mid<lim;mid<<=1){
int tmp=ksm(g,(mod-1)/(mid<<1));
for(int R=mid<<1,j=0;j<lim;j+=R){
for(int w=1,k=0;k<mid;k++,w=1ll*w*tmp%mod){
int x=f[j+k],y=1ll*w*f[j+k+mid]%mod;
f[j+k]=(x+y)%mod,f[j+k+mid]=(mod+x-y)%mod;
}
}
} if(g>3)
for(int in=ksm(lim),i=0;i<lim;i++) f[i]=1ll*f[i]*in%mod;
} void solve(int *a,int len){
if(len==1) return a[1]=1,void();
if(len&1){
solve(a,len-1);
for(int i=len;i;i--) a[i]=(1ll*a[i]*(len-1)%mod+a[i-1])%mod;
} else{
solve(a,len>>1); int mid=len>>1;
pw[0]=1;for(int i=1;i<=mid;i++) pw[i]=1ll*pw[i-1]*mid%mod;
lim=1;while(lim<=len) lim<<=1;
for(int i=1;i<lim;i++) rev[i]=(rev[i>>1]>>1)|(i&1?lim>>1:0);
for(int i=0;i<=mid;i++) c[i]=1ll*a[i]*fac[i]%mod,d[mid-i]=1ll*pw[i]*ifac[i]%mod;
ntt(c,3),ntt(d,3);
for(int i=0;i<lim;i++) c[i]=1ll*c[i]*d[i]%mod;
ntt(c,(mod+1)/3);
for(int i=0;i<=mid;i++) c[i]=1ll*c[mid+i]*ifac[i]%mod;
for(int i=mid+1;i<lim;i++) c[i]=0;
ntt(c,3),ntt(a,3);
for(int i=0;i<lim;i++) a[i]=1ll*a[i]*c[i]%mod;
ntt(a,(mod+1)/3);
for(int i=len+1;i<lim;i++) a[i]=0;
for(int i=0;i<lim;i++) d[i]=c[i]=0;
}
} int C(int n,int m){
return 1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
} void init(int n){
fac[0]=ifac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
ifac[n]=ksm(fac[n]);
for(int i=n-1;i;i--) ifac[i]=1ll*ifac[i+1]*(i+1)%mod;
} signed main(){
init(N-5); n=getint(),A=getint(),B=getint();
if(!A or !B or n-1<A+B-2 or A+B-2<A-1) return puts("0"),0;
if(n==1) return printf("1"),0;
solve(a,n-1);
printf("%lld\n",1ll*a[A+B-2]*C(A+B-2,A-1)%mod);
return 0;
}

[CF960G] Bandit Blues的更多相关文章

  1. CF960G Bandit Blues 【第一类斯特林数 + 分治NTT】

    题目链接 CF960G 题解 同FJOI2016只不过数据范围变大了 考虑如何预处理第一类斯特林数 性质 \[x^{\overline{n}} = \sum\limits_{i = 0}^{n}\be ...

  2. CF960G Bandit Blues 第一类斯特林数、NTT、分治/倍增

    传送门 弱化版:FJOI2016 建筑师 由上面一题得到我们需要求的是\(\begin{bmatrix} N - 1 \\ A + B - 2 \end{bmatrix} \times \binom ...

  3. 解题:CF960G Bandit Blues & FJOI 2016 建筑师

    题面1 题面2 两个题推导是一样的,具体实现不一样,所以写一起了,以FJOI 2016 建筑师 的题面为标准 前后在组合意义下一样,现在只考虑前面,可以发现看到的这a个建筑将这一段划分成了a-1个区间 ...

  4. CF960G Bandit Blues 分治+NTT(第一类斯特林数)

    $ \color{#0066ff}{ 题目描述 }$ 给你三个正整数 \(n\),\(a\),\(b\),定义 \(A\) 为一个排列中是前缀最大值的数的个数,定义 \(B\) 为一个排列中是后缀最大 ...

  5. CF960G Bandit Blues(第一类斯特林数)

    传送门 可以去看看litble巨巨关于第一类斯特林数的总结 设\(f(i,j)\)为\(i\)个数的排列中有\(j\)个数是前缀最大数的方案数,枚举最小的数的位置,则有递推式\(f(i,j)=f(i- ...

  6. [CF960G]Bandit Blues(第一类斯特林数+分治卷积)

    Solution: ​ 先考虑前缀,设 \(f(i, j)\) 为长度为 \(i\) 的排列中满足前缀最大值为自己的数有 \(j\) 个的排列数. 假设新加一个数 \(i+1\) 那么会有: \[ f ...

  7. CF960G Bandit Blues 第一类斯特林数+分治+FFT

    题目传送门 https://codeforces.com/contest/960/problem/G 题解 首先整个排列的最大值一定是 \(A\) 个前缀最大值的最后一个,也是 \(B\) 个后缀最大 ...

  8. Luogu P4609 [FJOI2016]建筑师&&CF 960G Bandit Blues

    考虑转化题意,我们发现其实就是找一个长度为\(n\)的全排列,使得这个排列有\(A\)个前缀最大值,\(B\)个后缀最大值,求方案数 我们考虑把最大值拎出来单独考虑,同时定义一些数的顺序排列为单调块( ...

  9. 【CF960G】Bandit Blues(第一类斯特林数,FFT)

    [CF960G]Bandit Blues(第一类斯特林数,FFT) 题面 洛谷 CF 求前缀最大值有\(a\)个,后缀最大值有\(b\)个的长度为\(n\)的排列个数. 题解 完完全全就是[FJOI] ...

随机推荐

  1. DNS: Internet’s Directory

    关于DNS 互联网上几乎一切活动都以DNS请求开始.DNS(Domain Name System)是Internet的目录.访问URL时,设备所要做的第一件事就是询问目录,根据域名查出IP地址. 查询 ...

  2. 进军微信小程序之准备工作

    小程序这么火,不去浪一浪怎么行?   更何况,现在微信“赦免”了个人认证,又更新了web开发工具,现在正是搞搞小程序的最佳时期! 那么一起来看看要做什么准备吧~   官方的文档很详细,可参考:小程序官 ...

  3. hdu 4069 垃圾数独

    首先dfs给每个格子分一个大的区块 其次套板子就a 我一开始直接在选取行的时候填数独,发现超时 我这一行也就4个元素,找到 x <= 81 的列计算元素位置,81 < x <= 16 ...

  4. 项目小程序笔记-登录界面+FPGA管脚分配文件生成

    声明:只是为了记录我遇到的一些问题,其中有我理解错的望勿参考. (1)qt designer设计好窗口 主窗口: 登录窗口: 关于qt designer的使用,大可以百度,很简单的,要注意的是部件的参 ...

  5. 深入理解JVM(一)——基本原理

    前言 JVM一直是java知识里面进阶阶段的重要部分,如果希望在java领域研究的更深入,则JVM则是如论如何也避开不了的话题,本系列试图通过简洁易读的方式,讲解JVM必要的知识点. 运行流程 我们都 ...

  6. .net core DI 注册 Lazy<> 类型

    当我们在 .net core (2.1) 中运行如下代码注入 Lazy<T> 变量的时候: public AccountService(Lazy<IHttpContextAccess ...

  7. react-native 项目初始化

    react-native 项目初始化 搭建java,android,node环境 http://www.cnblogs.com/morang/p/react-native-java-build.htm ...

  8. 【sql注入】简单实现二次注入

    [sql注入]简单实现二次注入 本文转自:i春秋社区 测试代码1:内容详情页面 [PHP] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 1 ...

  9. 【渗透攻防Web篇】SQL注入攻击高级

    前言 前面我们学习了如何寻找,确认,利用SQL注入漏洞的技术,本篇文章我将介绍一些更高级的技术,避开过滤,绕开防御.有攻必有防,当然还要来探讨一下SQL注入防御技巧. 目录 第五节 避开过滤方法总结 ...

  10. 新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析

    1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件 ...