【ybt金牌导航1-2-6】【luogu P2467】地精部落

地精部落

题目链接:ybt金牌导航1-2-6 / luogu P2467

题目大意

有一个排列,要使得每个位置要么都比两边高,要么比两边低。
而且一定要以一高一低的方式排列。
两边的只用比旁边的那个高或低就可以。
给出排列的长度 n 和模数,要你求出排列的种数在模数取模意义下的值。

思路

我们考虑设 \(f_{i,j}\) 为前 \(i\) 个数的排列,最后一个数是高的,然后是 \(j\) 会有的方案数。

那我们可以发现有几个特点:

  1. 高低高低和低高低高这两种形状的类型种数都是相同的。
    因为你可以把 \(a_1,b_1,a_2,b_2,a_3\) 看成 \(n-a_1+1,n-b_1+1,n-a_2+1,n-b_2+1,n-a_3+1\)
    那这个既是另一种类型了。
  2. 如果一个排列已经是满足的了,而且 \(i\)\(i+1\) 不相邻。那我们可以把他们互换,还是合法的。就因为他们不相邻,那它们就算交换了,它们还是原来的高低状态,就没有问题。

那我们可以求以高结尾的方案数,然后再输出乘 \(2\) 即可。
那怎么求高结尾的呢?
考虑根据上面的特点搞转移方程。
\(f_{i,j}=f_{i,j-1}+f_{i-1,n-j+1}\)
为什么呢?
按相不相邻来分开,不相邻的就可以交换直接形成新的波动,那就是把 \(j\)\(j-1\) 交换所能有的方案数,就是 \(f_{i,j-1}\)

那我们考虑如果相邻,是怎么样的。
那它就变成了 前 \(i-1\) 个数的排列,\(j-1\) 是最后一个,且是山谷的情况。因为这样你就可以直接让 \(j+1\sim i-1\) 的区间的数都加一(因为是相对的关系,加了之后还是满足高低关系),然后再在最后的位置把 \(j\) 插进去。
那你由前面可以知道,你前面是山谷,那你要把山谷改成山峰,那第二位就要变成 \((i-1)-(j-1)+1\),即 \(i - j+1\)。那就可以从 \(f[i-1][i-j+1]\) 转移过来。

然后你就得到了转移方程。

当然,两个 \(3500\) 的数组会炸空间,那我们观察到 \(i\) 这一维只会涉及前面的那一个,那就可以用滚动数组解决空间问题。

代码

#include<cstdio>

using namespace std;

int n, p, f[3][4201], ans;

int main() {
	scanf("%d %d", &n, &p);
	
	f[2 & 1][2] = 1;//一开始最后为高的只有这一种,初始化
	for (int i = 3; i <= n; i++)
		for (int j = 1; j <= i; j++) {
			f[i & 1][j] = (f[i & 1][j - 1] + f[(i - 1) & 1][i - j + 1]) % p;//dp
		}
	
	for (int i = 1; i <= n; i++)
		ans = (ans + f[n & 1][i]) % p;//最后可能以不同的数字结束
	
	printf("%d", (ans * 2) % p);
	
	return 0;
}
posted @ 2021-01-27 21:16  あおいSakura  阅读(159)  评论(0编辑  收藏  举报