题目描述
(3 le n le 500, P le 1e9)
解析
来自了邻桌硕佬的解法,感觉比题解好理解
可以被拆成(3)个上升子序列等价于最长下降子序列不超过(3)
依次考虑每个位置放哪个数,结合(O(n log n))求最长下降子序列的方法
设(dp(i, j, k))表示还剩下(i)个数要放,其中有(j)个数比长度为(1)的下降子序列的末尾最大元素(设为(a))大,(k)个比长度为(2)的下降子序列的末尾最大元素(设为(b))大
现在要放的数(x)分为三个部分:
- (x > a):
- 放了(x)之后(a)会被更新
- (b < x < a):
- 放了(x)之后(b)会被更新
- (x < b) :
- 如果放了(x),最长下降子序列长度变为(3),若(x)不是最小,之后不论怎么放都不满足条件
- 所以(x)只能是最小的那个
- 特判不存在这种情况的状态
那么就会有:
[dp(i, j, k) = sum_{h = 0}^{j - 1} dp(i- 1, h, k - 1) + sum_{h = j}^{k - 1}dp(i - 1, j, h) + [k
eq i]dp(i - 1, j, k)
]
两个(sum)都可以前缀和优化,然后就可以(O(n^3))啦!!开心一交,为什么(T)了??!!
嗯……计算次数那么多,每次调用全局变量多慢啊……干嘛不在三层(for)里面先拿个局部变量存一下当前答案呢……
还有加法就不要取模了……判一下大了就减吧……
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#define MAXN 505
typedef long long LL;
int f[2][MAXN][MAXN], sum1[2][MAXN][MAXN], sum2[2][MAXN][MAXN], N, P;
inline int add(int x, int y) { x += y; return x >= P ? x - P : x; }
inline int sub(int x, int y) { x -= y; return x < 0 ? x + P : x; }
inline void inc(int &x, int y) { x += y; if (x >= P) x -= P; }
inline void dec(int &x, int y) { x -= y; if (x < 0) x += P; }
int main() {
freopen("yuan.in", "r", stdin);
freopen("yuan.out", "w", stdout);
scanf("%d%d", &N, &P);
for (int i = 1; i <= N + 1; ++i){
for (int j = 1; j <= N + 1; ++j)
for (int k = j; k <= N + 1; ++k) {
int tmp;
if (i == 1 || k == 1) tmp = 1;
else {
tmp = sum1[(i ^ 1) & 1][j - 1][k - 1];
inc(tmp, sub(sum2[(i ^ 1) & 1][j][k - 1], sum2[(i ^ 1) & 1][j][j - 1]));
if (k ^ i) inc(tmp, f[(i ^ 1) & 1][j][k]);
}
f[i & 1][j][k] = tmp;
sum1[i & 1][j][k] = add(sum1[i & 1][j - 1][k], tmp);
sum2[i & 1][j][k] = add(sum2[i & 1][j][k - 1], tmp);
}
}
printf("%d
", f[(N ^ 1) & 1][N + 1][N + 1]);
return 0;
}
//Rhein_E 100pts