[小清新] [dp] P5999 kangaroo

posted on 2023-12-06 06:09:39 | under 题集 | source

题意

求出满足如下条件的 \(n\) 排列:

  1. 起点为 \(s\),终点为 \(t\)
  2. 对于每个元素 \(a_i\),都有 \(a_i>{a_{i-1},a_{i+1}}\)\(a_i<{a_{i-1},a_{i+1}}\)

思路

条件 \(2\) 很难满足,那么考虑围绕它来做:考虑从小到大插入元素,这样 \(i\) 插入到任意位置都是可以的,至少目前时这样的。

然后发现对于任意排列,都能每次找到最大的元素然后以它为中心把排列拆为两部分,不妨逆着做:把序列当成若干个连通块,每个连通块内部满足条件,那么 \(i\) 就可以连接相邻连通块。在保证 \(i\) 递增的情况下,这样肯定不会计重。

于是定义 \(f_{i,j}\) 表示前 \(i\) 个元素分为 \(j\) 个连通块的方案数。

事实上,对于“插入型 \(\rm dp\)”,要考虑的操作有:在块内插入元素、连接两个块、新增块。我们已经解决后两种操作,剩下第一种操作。

仔细想想,对于任意插入操作,都能被转化为后两种操作,于是舍去即可。

但是存在特殊情况不可转化:在最左边的块的左侧和最右边的块的右侧,只需特殊处理下就好。对于此题而言,分别对应的是 \(i=s,t\)

有些细节,具体见代码。

代码

#include<bits/stdc++.h>
using namespace std;

#define int long long
#define ADD(a, b) (a) = ((a) + (b)) % mod
const int N = 2e3 + 5, mod = 1e9 + 7;
int n, s, t, f[N][N]; 

signed main(){
	cin >> n >> s >> t;
	f[0][0] = 1;
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= i; ++j)
			if(i == s || i == t)
				ADD(f[i][j], f[i - 1][j] + f[i - 1][j - 1]);
			else
				ADD(f[i][j], f[i - 1][j + 1] * j + f[i - 1][j - 1] * (j - (i > s) - (i > t)));
				//注意有时头和尾不能放 
	cout << f[n][1];
	return 0;
}
posted @ 2026-01-13 11:24  Zwi  阅读(0)  评论(0)    收藏  举报