【香蕉oi】消失的序列(DP、组合数学)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/xyyxyyx/article/details/102704064

文章目录

题意

求有多少 1 1 n n 的排列,满足如下条件:

  1. p o s pos 位是 x x
  2. 以这个序列为入栈序列,可以只用一个栈,使用入栈出栈操作,使得出栈序列是升序。

思路

数数好题。

首先来发现性质:

  1. 对于序列里每一个数,都必须满足:他前面所有比他大的数组成的序列为降序。即不存在 i < j < k i<j<k 满足 p k < p i < p j p_k<p_i<p_j 。而且只要满足性质1,就一定是合法序列。
  2. 对于任意一个子序列,找到其中的最大值,必须满足:最大值左边的数小于最大值右边的数。同样也是充分必要条件。

然后利用性质1来DP。(考试的时候我一直试图用第2个性质搞出点东西,但是失败告终。。。)

d p [ i ] [ j ] dp[i][j] 表示第 i i n n 位已经填满,并且当前最小值是 j j 的方案数。

当没有第 p o s pos 位填 x x 的限制时,分类讨论第 i i 位填的数:

  1. i i 位填 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]
  2. i i 位填的数大于 j j ,那么为了满足性质1,第 i i 位必须填大于 j j 且没有填过的最小的那个数,否则这个未填过的最小数只能填在他的前面,那么这两个大于 j j 的数就不满足降序了。所以 d p [ i ] [ j ] = d p [ 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]

然后加上限制,显然当 i p o s i\neq pos 的时候递推式不变。

i = p o s i=pos 时,分类讨论 j j 的取值。

  1. j = x j=x 时,可以普通地DP,但是要去掉 d p [ p o s + 1 ] [ x ] dp[pos+1][x]
  2. j < x j<x 时,发现 [ j , x 1 ] [j,x-1] 这些数必须选,并且必须放在 [ p o s + 1 , p o s + 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] (其中 C a t i Cat_i 是第 i i 个卡特兰数,表示 [ j , x 1 ] [j,x-1] 的数的合法排列方案数)。

那么DP已经完成了。

然后可以把这个DP方程看成在一个二维的方阵里面走格点,如下图(题解里面贺的):

在这里插入图片描述

  • d p [ i ] [ j ] dp[i][j] 在第 i i 列第 j j
  • 一个位置的DP值就是从右上角走到当前点的路径数
  • 注意这条不能跨越的线比卡特兰数的那条往上挪了一格
  • 注意上图是没有考虑 a p o s = x a_{pos}=x 的限制的图,在有限制的图中,第 p o s pos 列上下之间没有连边

受卡特兰数的启发,我们可以用组合数快速地算出除了 p o s pos 这一列之外的某两个位置的之间的路径数。

那么我们只需要对于 p o s pos 这一列的每一个点,找到他从哪里更新过来,并且会对 d p [ 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;
}

猜你喜欢

转载自blog.csdn.net/xyyxyyx/article/details/102704064
今日推荐