大致题意: 给定一个序列,你可以从中任选\(k\)个区间将区间中的数都加\(1\),求能得到的最大的最长上升子序列长度。
暴力\(DP\)
首先一个显然的结论,每次操作区间右端点一定是\(n\)。这应该很好理解吧,毕竟一个数加了\(1\)总不会变劣。
那么就很容易想出一个暴力\(DP\):\(f_{i,j}\)表示以第\(i\)个数结尾、第\(i\)个数加了\(j\)时的最大的最长上升子序列长度。
转移就是枚举\(k,x\),满足\(k<i,x\le j\)且\(a_k+x\le a_i+j\),令\(f_{i,j}=max\{f_{k,x}\}+1\)。
但直接暴力枚举肯定\(T\)飞,需要想办法优化。
二维树状数组优化\(DP\)
其实这是一道还挺有意思的题目。
我们把\(f_{i,j}\)对应到二维平面上一个点\((j+1,a_i+j)\)(之所以要用\(j+1\)是为了避免出现\(0\))。
那么它的值其实就是它左下方所有元素最大值加\(1\)。
二维数点问题,自然想到二维树状数组。
什么?树状数组还能求最大值?
实际上我也是今天才知道,如果仅仅询问前缀最大值,树状数组是可以支持的,而且实现方法和区间求和的树状数组几乎无异。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 10000
#define K 500
#define V 5000
#define Gmax(x,y) (x<(y)&&(x=(y)))
using namespace std;
int n,k,a[N+5];
struct TwoD_TreeArray//二维树状数组
{
int a[K+5][V+K+5];
I void U(RI x,CI y,CI v) {for(RI i;x<=k+1;x+=x&-x) for(i=y;i<=V+k+1;i+=i&-i) Gmax(a[x][i],v);}//单点修改
I int Q(RI x,CI y,RI t=0) {for(RI i;x;x-=x&-x) for(i=y;i;i-=i&-i) Gmax(t,a[x][i]);return t;}//左下区间查询
}T;
int main()
{
RI i,j,ans=0;for(scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",a+i);
RI t;for(i=1;i<=n;++i) for(j=k;~j;--j) T.U(j+1,a[i]+j,t=T.Q(j+1,a[i]+j)+1),Gmax(ans,t);//DP转移
return printf("%d\n",ans),0;//输出答案
}