过了一年来看,这道题还是很妙。

FJOI2016 建筑师

小 Z 是一个很有名的建筑师,有一天他接到了一个很奇怪的任务:在数轴上建 \(n\) 个建筑,每个建筑的高度是 \(1\)\(n\) 之间的一个整数。

小 Z 有很严重的强迫症,他不喜欢有两个建筑的高度相同。另外小 Z 觉得如果从最左边(所有建筑都在右边)看能看到 \(A\) 个建筑,从最右边(所有建筑都在左边)看能看到 \(B\) 个建筑,这样的建筑群有着独特的美感。现在,小 Z 想知道满足上述所有条件的建筑方案有多少种?

如果建筑 \(i\) 的左(右)边没有任何建造比它高,则建筑 \(i\) 可以从左(右)边看到。两种方案不同,当且仅当存在某个建筑在两种方案下的高度不同。

对于 \(100 \%\) 的数据 :\(1 \leq n \leq 50000, \ 1 \leq A, B \leq 100, \ 1 \leq T \leq 200000\)

题解

https://www.luogu.com.cn/blog/WDLGZH2017/solution-p4609

首先\(A\)\(B\)的地位是对称的,我们可以先考虑只有\(A\)的限制条件怎么做。

\(dp[i][j]\)表示对于\(i\)个元素的排列,且\(A=j\)的方案数。

显然如果最小的数在第一位,那么就有1的贡献,否则没有,所以

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

然后我们考虑如何考虑\(B\)

我们知道,所有数都不大于\(n\),所以\(n\)左边的数才可能贡献到\(A\)\(n\)右边的数才可能贡献到\(B\),所以

\[Ans=\sum_{i=1}^n\begin{bmatrix}i-1\\A-1\end{bmatrix}\times \begin{bmatrix}n-i\\B-1\end{bmatrix}\times \binom{n-1}{i-1} \]

但是这个式子还是要进行优化的,不过有一点比较麻烦,第一类Stirling数连通项公式都不好求,怎么推式子呢?

我们可以用组合意义来证明等式。

\(Ans\)是在\(n-1\)个元素中先选出\(i-1\)个,然后再分别将\(i-1\)个和剩下的\(n-i\)个组成\(a-1\)\(b-1\)个圆排列(这是根据元素个数来枚举)

也是\(n-1\)个元素组成\(a+b-2\)个圆排列,然后再这\(a+b-2\)个圆排列中选\(a-1\)个(这是根据圆排列直接枚举),因此得知

\[Ans=\begin{bmatrix}n-1\\a+b-2\end{bmatrix}\times \binom{a+b-2}{a-1} \]

CF960G Bandit Blues

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

题解

对于 DP,除了插入最大的,我们还能插入最小的。

我们强制让原来的排列为 \(2 \sim n\),让要插入的数为 \(1\),这样我们就能惊喜地发现,好像可以设状态了。

\(f_{i, j}\) 表示由 \(i\) 个数组成且前缀最大值为 \(j\) 的排列个数,我们考虑插入一个数会发生什么。

如果我们把这个数放在最前面,那么前缀最大值会加一;如果我们把这个数放在其它位置,前缀最大值不变,因此我们有状态转移方程:

\[f_{i, j} = f_{i - 1, j - 1} + (i - 1) \times f_{i - 1, j} \]

但是这个和答案有什么联系呢?观察发现,整个排列中一定有一个最高点,且在最高点左边,我们有 \(j - 1\) 个前缀最大值。考虑将后面的序列反转,那么后缀最大值也转换为了前缀最大值的问题。也就是说,我们相当于要将两个排列组合起来。(好像很难想的样子)

我们枚举最大值的位置\(i+1\)和最大值左边选哪些数,就能得到最后的答案:

\[\mathrm{Ans} = \sum_{i = a - 1}^{n - b} f_{i, a - 1} \times f_{n - 1 - i, b - 1} \times \binom{n - 1}{i} \]

我们可以看看能否继续化简答案的\(O(n^2)\)式子,或者改变算式的形式。

首先对于每种满足要求的排列,我们按以下方式将\(1\dots n-1\)分成\(a+b-2\)组:
对于\(n\)的左边,每个作为前缀最大值的数的数一直到下一个作为的数或者\(n\)的前一位分为一组,右边类似。

然后我们去掉\(n\),把右边的翻转,再按组排序,发现得到的结果的方案数就是\(f_{n - 1, a + b - 2}\)。现在我们把\(n\)的插到第 \(a - 1\) 组之后,然后翻转右边那部分,这样我们就得到了一个符合题目要求的排列!所以我们找到了一个一一映射。

\[\mathrm{Ans} = f_{n - 1, a + b - 2} \times \binom{a + b - 2}{a - 1} \]

所以要求的就是\(f\),而我们早就发现他是轮换数。FFT预处理即可,时间复杂度\(O(n\log n)\)


不用DP式看出轮换数的话,也可以直接考虑组合意义。

去掉\(n\)之后,假设某组有\(i\)个数,那么除了最大的那个数,其他数可以随意排列,有\((i-1)!\)种方案,注意到\(i\)个数的轮换的方案数\(\begin{bmatrix}i \\ 1\end{bmatrix}=(i-1)!\),所以我们可以将这种分组看做把\(n-1\)个元素划分为\(a+b-2\)个轮换,方案数为\(\begin{bmatrix}n-1\\a+b-2\end{bmatrix}\)

以上并没有考虑\(n\),我们分好组后,还要决定把哪些组排在\(n\)的左边,方案数为\(\binom{a+b-2}{a-1}\)。所以最终答案殊途同归。

void num_trans(polynomial&a,int dir){
	int lim=a.size();
	static vector<int> rev,w[2];
	if(rev.size()!=lim){
		rev.resize(lim);
		int len=log2(lim);
		for(int i=0;i<lim;++i)
			rev[i]=rev[i>>1]>>1|(i&1)<<(len-1);
		for(int dir=0;dir<2;++dir){
			w[dir].resize(lim);
			w[dir][0]=1,w[dir][1]=fpow(g[dir],(mod-1)/lim);
			for(int i=2;i<lim;++i)	
				w[dir][i]=mul(w[dir][i-1],w[dir][1]);
		}
	}
	for(int i=0;i<lim;++i)
		if(i<rev[i]) swap(a[i],a[rev[i]]);
	for(int step=1;step<lim;step<<=1){
		int quot=lim/(step<<1);
		for(int i=0;i<lim;i+=step<<1){
			int j=i+step;
			for(int k=0;k<step;++k){
				int t=mul(w[dir][quot*k],a[j+k]);
				a[j+k]=add(a[i+k],mod-t),a[i+k]=add(a[i+k],t);
			}
		}
	}
	if(dir){
		int ilim=fpow(lim,mod-2);
		for(int i=0;i<lim;++i) a[i]=mul(a[i],ilim);
	}
}

co int N=200000+1;
int fac[N],ifac[N];

polynomial solve(int n){ // [0,n]
	if(!n) return polynomial(1,1);
	if(n==1) {
		polynomial a(2);
		return a[1]=1,a;
	}
	int len=n>>1;
	polynomial a=solve(len);
	polynomial b(len+1),c(len+1);
	for(int i=0;i<=len;++i) b[i]=mul(a[i],fac[i]);
	for(int i=0;i<=len;++i) c[len-i]=mul(fpow(len,i),ifac[i]);
	int lim=1<<int(ceil(log2((len<<1)+1)));
	b.resize(lim),c.resize(lim);
	num_trans(b,0),num_trans(c,0);
	for(int i=0;i<lim;++i) c[i]=mul(c[i],b[i]);
	num_trans(c,1);
	b.resize(len+1);
	for(int i=0;i<=len;++i) b[i]=mul(c[i+len],ifac[i]);
	a.resize(lim),b.resize(lim);
	num_trans(a,0),num_trans(b,0);
	for(int i=0;i<lim;++i) a[i]=mul(a[i],b[i]);
	num_trans(a,1),a.resize((len<<1)+1);
	if(n&1){
		a.resize(n+1);
		int x=a[0],y;
		for(int i=1;i<=n;++i,x=y)
			y=a[i],a[i]=add(x,mul(n-1,y));
	}
	return a;
}
int main(){
	int n=read<int>(),a=read<int>(),b=read<int>();
	fac[0]=1;
	for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
	ifac[n]=fpow(fac[n],mod-2);
	for(int i=n-1;i>=0;--i) ifac[i]=mul(ifac[i+1],i+1);
	polynomial f=solve(n-1);f.resize((n<<1)-1);
	printf("%d\n",mul(f[a+b-2],mul(fac[a+b-2],mul(ifac[a-1],ifac[b-1]))));
	return 0;
}