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;
}
posted @ 2019-11-14 11:20  MorsLin  阅读(95)  评论(0编辑  收藏  举报