题目分析:
这是一道十分显然的动规题。
在我们定动态规划的状态前,我们先来考虑每次拔高的区间。由于要求最长的单调不下降序列,那么每次拔高的右区间应该都是N呢,因为这样才不会使右边的单调关系不受影响。
让我们用以下数据来举个例子:
4 3
4 2 1 3
如果我们拔高区间[2,2]两次,高度关系就变为了:
4 4 1 3
我们发现虽然第二株玉米已经不小于第一株玉米,但是此时第二株玉米已经高余了第四株玉米,最后的答案是没有变的,而很显然我们可以在拔高区间[2,2]的同时拔高[3,4],也就是拔高[2,4],因为此时无论拔高[2,4]多少次,第二、三、四株的高度关系都是不变的,所以我们拔高的区间只能是如下:
4213
213
13
3
有了这一个结论之后,我们不难定出一个很普通的状态:
DP[I][J]表示前I棵一共拔了J次
答案显然为DP[N][K]
那么如下的暴力状态转移就很容易得到了:
由于这样的状态转移方程的理解过于简单,所以此处不做解释。
我在上面已经说了,这是一个很显然的暴力方程,我们应该考虑一下优化。
DP[I][J]最终要取到DP[P][Q]中的最大值,所以我们考虑用某种东西记录转移部分的最大值,但是同时我们还需要处理拔高的次数,所以我们在处理一个数组:
G[I][J]表示一共拔高了I次,其中最高的一株高度为J
所以我们大概变换了一下状态转移方程:
但是这样看来,状态转移方程似乎并没有优化,但是真正的优化其实在G[][]这个数组,因为我们要求的是这个转移区间的最大值,所以我们考虑将G[][]这个数组当做二维树状数组来进行优化处理最大值。
所有我们在寻找最大值答案的时候不妨直接处理P,Q的极值(更新树状数组同此),即:
DP[I][J]=GetAns(J,Height[I]+J)+1;
由于树状数组的LowBit操作,GetAns(J,Height[I]+J)已经处理完了当前的所有区间
讲到这里,代码其实就很好写了,由于树状数组的下标是不为0的,但是这道题当中处理的时候是需要的,所以我们不妨将J强行加一位,即K++;
参考代码:
void Update(LL X,LL Y,LL Z){
LL I,J;
for(I=X;I<=K;I+=LowBit(I)){
for(J=Y;J<=M;J+=LowBit(J)){
Bit[I][J]=max(Bit[I][J],Z);
}
}
}
LL GetAns(LL X,LL Y){
LL I,J,Ans=0;
for(I=X;I>0;I-=LowBit(I)){
for(J=Y;J>0;J-=LowBit(J)){
Ans=max(Ans,Bit[I][J]);
}
}
return Ans;
}
int main(){
LL I,J;
N=Read(),K=Read();
for(I=1;I<=N;I++){
Height[I]=Read();M=max(M,Height[I]);
}M+=K;K++;
for(I=1;I<=N;I++){
for(J=1;J<=K;J++){
DP[I][J]=GetAns(J,Height[I]+J)+1;
Update(J,Height[I]+J,DP[I][J]);
Ans=max(Ans,DP[I][J]);
}
}
Write(Ans);
return 0;
}
更省空间的写法:
int main(){
LL I,J;
N=Read(),K=Read();
for(I=1;I<=N;I++){
Height[I]=Read();M=max(M,Height[I]);
}M+=K;K++;
for(I=1;I<=N;I++){
for(J=1;J<=K;J++){
LL Tmp=GetAns(J,Height[I]+J)+1;
Update(J,Height[I]+J,Tmp);
Ans=max(Ans,Tmp);
}
}
Write(Ans);
return 0;
}