地精部落
题目链接:ybt金牌导航1-2-6 / luogu P2467
题目大意
有一个排列,要使得每个位置要么都比两边高,要么比两边低。
而且一定要以一高一低的方式排列。
两边的只用比旁边的那个高或低就可以。
给出排列的长度 n 和模数,要你求出排列的种数在模数取模意义下的值。
思路
我们考虑设 f i , j f_{i,j} fi,j 为前 i i i 个数的排列,最后一个数是高的,然后是 j j j 会有的方案数。
那我们可以发现有几个特点:
- 高低高低和低高低高这两种形状的类型种数都是相同的。
因为你可以把 a 1 , b 1 , a 2 , b 2 , a 3 a_1,b_1,a_2,b_2,a_3 a1,b1,a2,b2,a3 看成 n − a 1 + 1 , n − b 1 + 1 , n − a 2 + 1 , n − b 2 + 1 , n − a 3 + 1 n-a_1+1,n-b_1+1,n-a_2+1,n-b_2+1,n-a_3+1 n−a1+1,n−b1+1,n−a2+1,n−b2+1,n−a3+1。
那这个既是另一种类型了。 - 如果一个排列已经是满足的了,而且 i i i 和 i + 1 i+1 i+1 不相邻。那我们可以把他们互换,还是合法的。就因为他们不相邻,那它们就算交换了,它们还是原来的高低状态,就没有问题。
那我们可以求以高结尾的方案数,然后再输出乘
2
2
2 即可。
那怎么求高结尾的呢?
考虑根据上面的特点搞转移方程。
f
i
,
j
=
f
i
,
j
−
1
+
f
i
−
1
,
n
−
j
+
1
f_{i,j}=f_{i,j-1}+f_{i-1,n-j+1}
fi,j=fi,j−1+fi−1,n−j+1
为什么呢?
按相不相邻来分开,不相邻的就可以交换直接形成新的波动,那就是把
j
j
j 和
j
−
1
j-1
j−1 交换所能有的方案数,就是
f
i
,
j
−
1
f_{i,j-1}
fi,j−1。
那我们考虑如果相邻,是怎么样的。
那它就变成了 前
i
−
1
i-1
i−1 个数的排列,
j
−
1
j-1
j−1 是最后一个,且是山谷的情况。因为这样你就可以直接让
j
+
1
∼
i
−
1
j+1\sim i-1
j+1∼i−1 的区间的数都加一(因为是相对的关系,加了之后还是满足高低关系),然后再在最后的位置把
j
j
j 插进去。
那你由前面可以知道,你前面是山谷,那你要把山谷改成山峰,那第二位就要变成
(
i
−
1
)
−
(
j
−
1
)
+
1
(i-1)-(j-1)+1
(i−1)−(j−1)+1,即
i
−
j
+
1
i - j+1
i−j+1。那就可以从
f
[
i
−
1
]
[
i
−
j
+
1
]
f[i-1][i-j+1]
f[i−1][i−j+1] 转移过来。
然后你就得到了转移方程。
当然,两个 3500 3500 3500 的数组会炸空间,那我们观察到 i i 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;
}