提高组(计数)

题目链接

类题:冒泡排序

求长度为 (n) 的排列中满足最长下降子序列长度不超过 2 ,且符合(p_x=y) 的排列数。(n le 10^7,T le 10^6)

题意转化:不存在三个点,使得左边的点比中间大,右边的点比中间小。

我们要知道一个 trick : 从大到小/从小到大枚举数,尝试将其插入当前排列,并使之合法。这样的话我们在插入的时候只需要保证如果前面有比它大的的话,以后比它小的就不能放后边了。由于我们从大到小放,当前序列上的数都比他大,之后的数都比他小,于是我们要么放最前面,要么放到某个位置,并且要求以后放的数都要在它之前。

我们发现这可能会有一个“只能放在前 (j) 个数之后”的限制。于是设置DP状态:(f_{i,j}) 表示已经放了 (i) 个数,要求以后只能放在前 (j) 个数之后(或者最前面)的方案数,那么转移就比较显然了:

放最前面:

[f_{i,j} o f_{i+1,j+1} ]

放第 (k) 个的后面:

[f_{i,j} o f_{i+1,k},kle j ]

总的来说:

[f_{i,j} o f_{i+1,k},kle j+1 ]

如果我们在一张网格图上看,这像是网格图上的路径;经过一些转化,我们发现转移其实就是只能向右上走,且不能穿过 (y=x) 的一条条路径,一次转移相当于向右走一步,再向上走若干步。

然后题目还要求 (p_x=y)。如果 (x=y),那么第 (x) 个之前一定都是比 (y) 小的,第 (x) 个之后一定都是比 (y) 大的。直接单独用卡特兰数算后乘起来即可。

如果 (x > y),那么我们在放第 (y) 小的数的时候就不能让它放到最前面了,否则仅剩的 (y-1) 个数不足以支持其成为第 (x) 个数。那么他只能放到某一个数的后面,这样的话以后比他小的数就都会放到他的前面,那么这个数到底插到第几个数的后面就知道了,即能算出一个数 (k),使得所有第 (x-1) 列的状态都要转移到 (f_{x,k})。然后问题转化为了一个网格图上必经一点的“卡特兰数”,亦用翻折法可解(根据 (y=x+1) 对称)。

如果 (x < y),看起来它是可以放到最前面的,并且对以后的影响也不太好处理。不过这种问题实际上可以转化为 (x gets n - x + 1, y gets n - y + 1) 的问题(全部取反后成为反向问题)

下面给出一些关键参数:

由DP转化为“卡特兰数”网格图的方法:

[(x,y) o (x-1, x-y) ]

这种转化方法将“放在最前面”映射成了横着走

必经的那个点:

[(n-y,n-x+1) ]

根据 (y=x+1) 翻折:

[(x,y) o (y-1,x+1) ]

路径:

[(0,0) o (n-y,n-x+1) o (n,n) ]

注意,我们不仅要求经过一点,还要求经过前一步不能横着走过来,经过的后一步要先向右走一步。

关键代码:

inline ll calc(int n, int m) {
	ll res = (get_c(n + m, n) - get_c(n + m, n + 1) + P) % P;
	return res;
}

inline void work() {
	int n, x, y; read(n), read(x), read(y);
	if (x == y) {
		ll ans = calc(x - 1, x - 1) * calc(n - x, n - x) % P;
		printf("%lld
", (ans % P + P) % P);
		return ;
	}
	if (x < y)	x = n - x + 1, y = n - y + 1;
	ll ans = (calc(n - y, n - x + 1) - (x == y + 1 ? 0 : calc(n - y - 1, n - x + 1))) * (get_c(y-1+x-1,y-1) - get_c(y-2+x,x)) % P;
	//Attention!!! 
	printf("%lld
", (ans % P + P) % P);
}
原文地址:https://www.cnblogs.com/JiaZP/p/13666788.html