题目链接。
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
", (f[n & 1][n][0] + f[n & 1][n][1]) % P);
return 0;
}