SCOI2014 方伯伯的玉米田
我可以说方伯伯浪费粮食么……
首先要知道一个结论:
每次操作的右端点一定是\(n\)
因为如果拔高一段区间,区间内部的玉米的相对高度不会发生变化,区间左边的点贡献增加\(x\),但区间右边的点贡献会减少。如果我们将这段拔高区间的右端点挪到\(n\)上,区间左边的点贡献还是会增加\(x\),区间右边不存在点,也就是贡献不会减少。所以当左端点固定时,右端点是\(n\)能保证最优
然后我们就可以比较容易的写出一个状态转移方程:
设f[i][k]
表示以i
为结尾,用了k
次拔高操作后的最长不下降子序列的长度
则f[i][k] = max { f[j][l] } + 1 (0 < j < i, 0 <= l <= k, h[i] + k >= h[j] + l)
暴力转移是\(O(nk)\)的。对于f[i][k]
我们显然要求一个二维前缀最大值,可以用二维树状数组来优化,这样就可以优化成\(O(\log{n}\log{k})\),总时间复杂度为\(O(nk\log{n}\log{k})\)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
#define lb(__a) (__a & (-__a))
using namespace std;
LL read() {
LL k = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9')
k = k * 10 + c - 48, c = getchar();
return k * f;
}
int n, k, tree[20010][510], maxn;
int Maxn(int x, int y) {
int anss = 0;
for(int i = x; i; i -= lb(i))
for(int j = y; j; j -= lb(j))
anss = max(anss, tree[i][j]);
return anss;
}
void update(int x, int y, int val) {
for(int i = x; i <= maxn + k; i += lb(i))
for(int j = y; j <= k+1; j += lb(j))
tree[i][j] = max(tree[i][j], val);
}
int h[10010], ans;
int main() {
n = read(), k = read();
for(int i = 1; i <= n; ++i)
h[i] = read(), maxn = max(h[i], maxn);
for(int i = 1; i <= n; ++i)
for(int j = k; j >= 0; --j) {
int x = Maxn(h[i]+j, j+1) + 1;
ans = max(ans, x);
update(h[i]+j, j+1, x);
}
cout << ans << endl;
return 0;
}