大致题意: 给定一个序列,你可以从中任选(k)个区间将区间中的数都加(1),求能得到的最大的最长上升子序列长度。
暴力(DP)
首先一个显然的结论,每次操作区间右端点一定是(n)。这应该很好理解吧,毕竟一个数加了(1)总不会变劣。
那么就很容易想出一个暴力(DP):(f_{i,j})表示以第(i)个数结尾、第(i)个数加了(j)时的最大的最长上升子序列长度。
转移就是枚举(k,x),满足(k<i,xle j)且(a_k+xle 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
",ans),0;//输出答案
}