Luogu 2467 [SDOI2010]地精部落
挺有意思的题。
优质题解: https://www.luogu.org/blog/user55639/solution-p2467
题意为求长度为n,取值为$[1, n]$的波动序列的个数。
首先需要三个性质:
性质1:在一个波动序列中,如果数字$i$与数字$i - 1$不相邻,那么把$i$与$i - 1$交换之后也会构成一个波动序列
性质2:如果已经构造好了一个波动序列,那么把这个序列中的每一个数$a_{i}$全部变成$(n + 1 - a_{i})$也是一个波动序列,且山谷和山峰的位置相反
性质3:一个波动序列倒过来也是一个合法的波动序列
首先根据性质3,我们只要算出以山峰开头的波动序列的个数,然后乘以二就是最终的答案了。
我们设$f_{i, j}$表示选用区间$[1, i]$中的数,且数字$j$开头为山峰的方案数。
考虑转移: $f_{i, j} = f_{i, j - 1} + f_{i - 1, i - j + 1}$ $(2\leq j\leq i )$
解释一下这个转移方程,由于性质1,当数字$j$与数字$j - 1$不相邻的时候,我们直接交换数字$j$和数字$j - 1$可以构造出合法的波动序列
考虑数字$j$与数字$j - 1$相邻的情况,这时候我们构造的序列相当于这样
$(j), (j - 1), ..., ..., ...$
因为$j$一定是一个山峰的位置,那么$j - 1$一定是一个山谷的位置,那么相当于我们要加上数字取值在$[1, j - 1] \cup [j + 1, i]$且以数字$j - 1$开头的山谷的数量
我们的状态设计并不能拆分区间,但是我们可以考虑把$[j + 1, i]$区间的数字全部都向左平移一位,这样并不会改变构造的波动序列的合法性
由于性质2,我们所求的这一段答案就是$f_{i - 1, (i - 1) + 1 - (j - 1) = i - j + 1}$。
考虑到1开头作山峰的时候没有合法的波动序列,所以初态从2开始。
滚掉第一维即可。
时间复杂度$O(n ^ {2})$
思维量和码量的差距还是很大的。
Code:
#include <cstdio> using namespace std; const int N = 4205; int n, P, f[2][N]; int main() { scanf("%d%d", &n, &P); f[0][2] = 1; for(int i = 3; i <= n; i++) for(int j = 2; j <= i; j++) f[i & 1][j] = (f[i & 1][j - 1] + f[(i - 1) & 1][i - j + 1]) % P; int ans = 0; for(int i = 2; i <= n; i++) ans = (ans + f[n & 1][i]) % P; printf("%d\n", ans * 2 % P); return 0; }
我真的不会数数呜呜呜……