方伯伯的玉米田「SCOI2014」

题意

给你 (n) 个数,(k) 次操作,每次可以选则一段区间全部加一,最后问最长上升子序列最长可以是多长。

Analysis

考虑每次加的区间,若是中间的一段,则把后面一段一起加一肯定不会更劣,所以每次修改必然是一个后缀。

然后可以得到一个动态规划的算法:设子状态为 (dp_{i,j}) 代表到第 (i) 个位置,总共操作了 (j) 次。

(dp_{i,j}=max(dp_{p,q}+1)) 其中 (a_p+qle a_i+j,jle q)

一个二维的最长上升子序列问题,我们可以用二维树状数组优化到 (O(nklog{v}log{k}))

注意树状数组从 (1) 开始而我们的 (j) 是可以为 (0) 的,所以每次要加一,另外为了保证第一维(这其实是个三维偏序),我们第二层循环(j)要从大到小。

#include <bits/stdc++.h>
using namespace std;
int n, k;
int a[10010];
int dp[10010][510], f[10010][510];
int ans;
void cmax(int &x, int y) {
	x = max(x, y);
}
int lowbit(int x) {
	return x & (-x);
}
void change(int x, int y, int cur) {
	for (int i = x; i <= 10000; i += lowbit(i)) {
		for (int j = y; j <= k + 1; j += lowbit(j)) {
			cmax(dp[i][j], cur);
		}
	}
}
int ask(int x, int y) {
	int ret = 0;
	for (int i = x; i; i -= lowbit(i)) {
		for (int j = y; j; j -= lowbit(j)) {
			cmax(ret, dp[i][j]);
		}
	}
	return ret;
}
int main() {
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for (int i = 1; i <= n; i++) {
		for (int j = k; j >= 0; j--) {
			f[i][j] = ask(a[i] + j, j + 1) + 1;
			change(a[i] + j, j + 1, f[i][j]);
			ans = max(ans, f[i][j]);
		}
	}
	printf("%d", ans);
	return 0;
}
原文地址:https://www.cnblogs.com/zcr-blog/p/13670156.html