P3287 [SCOI2014]方伯伯的玉米田

题目描述

有一个数列,选 \(k\) 个区间加 \(1\) ,使最后的最长不下降子序列(后文简称LIS)最长

分析

首先,我们要证明一个性质:每一次的拔高操作区间右端点一定是最右边的玉米。
假如拔高的区间两边都有玉米,就有三种情况。

  • 拔高区间内比它们高的玉米还是比它们高。
  • 拔高区间内比它们矮的玉米不再比它们矮。
  • 拔高区间内比它们矮的玉米还是比它们矮。

对于这三种情况对于两边来说,左边会使LIS的长度不会减少甚至有可能增加,右边会使LIS的长度不会增加甚至有可能减少。
有了这个性质,我们可以想到这是一个dp优化的题。

状态

f[i][j]表示以 \(i\) 结尾,\(i\) 这个点已经被 \(j\) 个拔高区间覆盖过,所能得到的最长不下降序列长度。

转移方程

\(f[i][j]=max{f[k][l]}+1\) \((a[i]+j>=a[k]+k)\)
最后的条件告诉我们,为了确保找到的最长不下降子序列的最后一个点拔高后的高度不会超过当前这个点拔高后的高度,而仅仅使用两维我们无法进行高度的判定,所以这里必须增加一维来进行高度的判定,这一维表示的正是高度。但我们可以用从左往右的方法把第一维去掉
最后我们的状态转移方程是\(f[j][k]=max{f[k][l]}+1\) \((a[i]+j>=a[k]+k)\)

优化

我们发现上面的方法时复杂度是 \(O(n^2k^2)\),有没有方法可以优化呢?
可以看的出这其实就是求一个二维前缀的最值问题,所以我们就引入二位树状数组:
f[i][j]维护的是以高度(拔高后的高度)在 \((i-lowbit(i)+1,i)\),被 \((j-lowbit(j)+1,j)\) 个拔高区间覆盖过的点为结尾的所有不下降序列长度的最大值
于是...
f[j][k]表示当前这个点 \(i\) 前面(从左往右枚举点),以一个高度(拔高后的高度)不超过 \(j(j=a[i]+k)\),被不超过k个拔高区间覆盖过的点为结尾的最长不下降序列的长度
注意:\(k\) 的循环需要倒着枚,原因背包dp

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10,M=6e4+10,NN=1e3;
int n,k,maxn,ans;
int a[N],f[M][NN];
int query(int x,int y)
{
	int ans=0;
	for(int i=x;i;i-=(i&(-i)))
		for(int j=y;j;j-=(j&(-j)))
			ans=max(ans,f[i][j]);
	return ans;
}
void update(int x,int y,int w)
{
	for(int i=x;i<=maxn+k;i+=(i&(-i)))
		for(int j=y;j<=k+1;j+=(j&(-j)))
			f[i][j]=max(f[i][j],w);
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),maxn=max(maxn,a[i]);
	for(int i=1;i<=n;i++)
		for(int j=k;j>=0;j--)
		{
			int x=query(a[i]+j,j+1)+1;
			ans=max(ans,x);
			update(a[i]+j,j+1,x);
		}
	printf("%d\n",ans);
}
posted @ 2023-01-10 11:22  Gdfzlcx  阅读(65)  评论(0)    收藏  举报