题目链接。
Description
求长度为 \(N\) 的排列,满足对于每一个数,要么两边都比他高,要么两边都比他低的的方案数,对 \(P\) 取模。
Solution
是 AcWing 309. 装饰围栏 的弱化版。
考虑用 \(n - 1\) 长度的序列,在右边填上一个数,推导至长度为 \(n\),相当于长度集合的变化,DP:
状态设定
设 \(f_{i, j, p}\) 为长度为 \(i\),最右边的数的排名为 \(j\) 的方案数,\(p = 0 / 1\) 表示最右边这个数是山峰还是山谷。
状态转移
-
考虑最右边的数是山峰:\(f_{i,j,1} \gets \sum_{k=1}^{j-1} f_{i - 1, k, 0}\)
-
考虑最右边的是山谷,同理:\(f_{i, j, 0} \gets \sum_{k=j}^{i - 1} f_{i-1, k, 1}\)
转移也可以理解为一个排列的映射:相当于将前 \(i - 1\) 个序列中数值在 \([j, i - 1]\) 的数都 \(+1\),值域成为 \([j + 1, i]\),我们又新填入了一个数 \(j\),所以是一个 \(1\) ~ \(i\) 的排列
时空复杂度
时间:注意到这是一个区间和形式,用前缀和优化每次转移 \(O(1)\),所以总复杂度 \(O(N^2)\)。
空间:这题空间会炸,要开滚动数组。
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
const int N = 4205;
int n, P, f[2][N][2];
int main() {
scanf("%d%d", &n, &P);
f[1][1][1] = f[1][1][0] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i; j++) {
f[i & 1][j][0] = ((LL)f[(i - 1) & 1][i - 1][1] - f[(i - 1) & 1][j - 1][1] + f[i & 1][j - 1][0] + P) % P;
f[i & 1][j][1] = (f[(i - 1) & 1][j - 1][0] + f[i & 1][j - 1][1]) % P;
}
}
printf("%d\n", (f[n & 1][n][0] + f[n & 1][n][1]) % P);
return 0;
}