题意
求有多少 1 1 1到 n n n的排列,满足如下条件:
- 第 p o s pos pos位是 x x x
- 以这个序列为入栈序列,可以只用一个栈,使用入栈出栈操作,使得出栈序列是升序。
思路
数数好题。
首先来发现性质:
- 对于序列里每一个数,都必须满足:他前面所有比他大的数组成的序列为降序。即不存在 i < j < k i<j<k i<j<k满足 p k < p i < p j p_k<p_i<p_j pk<pi<pj。而且只要满足性质1,就一定是合法序列。
- 对于任意一个子序列,找到其中的最大值,必须满足:最大值左边的数小于最大值右边的数。同样也是充分必要条件。
然后利用性质1来DP。(考试的时候我一直试图用第2个性质搞出点东西,但是失败告终。。。)
设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示第 i i i到 n n n位已经填满,并且当前最小值是 j j j的方案数。
当没有第 p o s pos pos位填 x x x的限制时,分类讨论第 i i i位填的数:
- 第 i i i位填 j j j,那么无论如何都可以满足性质1,所以 d p [ i ] [ j ] = ∑ k = j + 1 i + 1 d p [ i + 1 ] [ k ] dp[i][j]=\sum_{k=j+1}^{i+1}dp[i+1][k] dp[i][j]=∑k=j+1i+1dp[i+1][k]
- 第 i i i位填的数大于 j j j,那么为了满足性质1,第 i i i位必须填大于 j j j且没有填过的最小的那个数,否则这个未填过的最小数只能填在他的前面,那么这两个大于 j j j的数就不满足降序了。所以 d p [ i ] [ j ] = d p [ i + 1 ] [ j ] dp[i][j]=dp[i+1][j] dp[i][j]=dp[i+1][j]
最终: d p [ i ] [ j ] = ∑ k = j i + 1 d p [ i + 1 ] [ k ] dp[i][j]=\sum_{k=j}^{i+1}dp[i+1][k] dp[i][j]=∑k=ji+1dp[i+1][k]。
然后加上限制,显然当 i ≠ p o s i\neq pos i=pos的时候递推式不变。
当 i = p o s i=pos i=pos时,分类讨论 j j j的取值。
- j = x j=x j=x时,可以普通地DP,但是要去掉 d p [ p o s + 1 ] [ x ] dp[pos+1][x] dp[pos+1][x]。
- j < x j<x j<x时,发现 [ j , x − 1 ] [j,x-1] [j,x−1]这些数必须选,并且必须放在 [ p o s + 1 , p o s + x − j ] [pos+1,pos+x-j] [pos+1,pos+x−j]的位置上(反证法),但是位置无需固定。所以 d p [ p o s ] [ j ] = C a t x − j ∗ ∑ k = x + 1 p o s + x − j + 1 d p [ p o s + x − j + 1 ] [ k ] dp[pos][j]=Cat_{x-j}*\sum_{k=x+1}^{pos+x-j+1}dp[pos+x-j+1][k] dp[pos][j]=Catx−j∗∑k=x+1pos+x−j+1dp[pos+x−j+1][k](其中 C a t i Cat_i Cati是第 i i i个卡特兰数,表示 [ j , x − 1 ] [j,x-1] [j,x−1]的数的合法排列方案数)。
那么DP已经完成了。
然后可以把这个DP方程看成在一个二维的方阵里面走格点,如下图(题解里面贺的):
- d p [ i ] [ j ] dp[i][j] dp[i][j]在第 i i i列第 j j j行
- 一个位置的DP值就是从右上角走到当前点的路径数
- 注意这条不能跨越的线比卡特兰数的那条往上挪了一格
- 注意上图是没有考虑 a p o s = x a_{pos}=x apos=x的限制的图,在有限制的图中,第 p o s pos pos列上下之间没有连边
受卡特兰数的启发,我们可以用组合数快速地算出除了 p o s pos pos这一列之外的某两个位置的之间的路径数。
那么我们只需要对于 p o s pos pos这一列的每一个点,找到他从哪里更新过来,并且会对 d p [ 1 ] [ 1 ] dp[1][1] dp[1][1]有多少贡献就行了。
重点在DP,DP思路还是挺厉害的。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7, N = 2e6 + 10;
int n, p, x;
int fc[N], ifc[N], ans;
template<class T>inline void read(T &x){
x = 0; bool fl = 0; char c = getchar();
while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
if (fl) x = -x;
}
inline int fpow(int x, int y, int p){
int ret = 1;
while (y){
if (y & 1) ret = 1LL * ret * x % p;
x = 1LL * x * x % p;
y >>= 1;
}
return ret;
}
void init()
{
fc[0] = 1;
for (int i = 1; i <= n*2; ++ i)
fc[i] = 1LL * fc[i-1] * i % mod;
ifc[n*2] = fpow(fc[n*2], mod-2, mod);
for (int i = n*2-1; i >= 0; -- i)
ifc[i] = 1LL * ifc[i+1] * (i+1) % mod;
}
int C(int n, int m){
if (m > n || m < 0) return 0;
return 1LL * fc[n] * ifc[m] % mod * ifc[n-m] % mod;
}
int Cat(int n){
return (C(2*n, n) - C(2*n, n-1) + mod) % mod;
}
int F(int n, int m){
if (n > m) swap(n, m);
return (C(m+n, n) - C(m+n, n-2) + mod) % mod;
}
int main()
{
read(n); read(p); read(x);
init();
ans = 0;
for (int i = 1, ub = min(x, p); i <= ub; ++ i){
if (i == x){
(ans += 1LL * (F(p-1, i-1)-F(p-1, i-2)+mod) * (F(n-p, n-x)-F(n-(p+1), n-x)+mod) % mod) %= mod;
}
else if (i < x){
int nxti = p+x-i;
if (nxti <= n) (ans += 1LL * (F(p-1, i-1)-F(p-1, i-2)+mod) * (F(n-nxti, n-x)-F(n-(nxti+1), n-x)+mod) % mod * Cat(x-i) % mod) %= mod;
}
}
printf("%d\n", ans);
return 0;
}