SCOI2014 方伯伯的玉米田 题解

可能其它还没写完的几篇随笔要搁一会儿,反正先写写题解

\(1-n\)的一个序列,值分别为\(a[1]\)\(a[2]...a[n]\),最多可进行\(K\)次操作,每次操作可以使\(l-r\)区间内的所有元素值加一(\(l\)\(r\)自选)。操作完后,还可以删除任意多个元素(不需要连续),使剩下的元素构成一个不下降序列,问这个子序列最多有多少个元素

首先,经过初步思考与推测可以得出一个结论,就是每次操作的区间中\(r\)一定为\(n\),因为最终要构成的是一个不下降的子序列,所以若\(r\neq n\),那么后面的元素相对于前面的元素就会下降多个单位,最终导致低于前面的元素而被删除,所以剩下的元素也会相应减少,不满足最优的条件

有了这个性质以后,我们便可以设\(dp[i][j]\)表示前\(i\)个元素共计被加了\(j\)次后构成的最后一个元素的下标为\(i\)为最长不下降子序列的长度(为什么\(i\)一定要选?因为若是不选那么我们就不知道当前序列最后一个元素是什么,然后就无法在转移时比较高度来构成不下降序列了),那么显然,第\(i\)个元素一定被加了\(j\)次,所以其高度为\(a[i]+j\)。(其实也可以理解成前\(i\)个元素被加了\(j\)次并在\(1-i\)中移除了部分且移除了\(i+1-n\)的全部元素使得剩下的元素变成不下降的序列)

接下来状态转移,首先二重循环枚举\(i\)\(j\)少不了,然后再枚举\(k\)\(p\),表示要从\(dp[k][p]\)这个状态转移过来(相当于\(i+1-k-1\)的元素都被删掉了),所以我们有如下的状态转移方程:

\(dp[i][j]=max\left\{dp[k][p]+1\right\},a[k]+p \le a[i]+j,p \le j,k<i\)

不幸的是,这样转移的复杂度是\(O(n^2k^2)\)的,实在是难以接受,所以还是要优化一下。发现我们每次只是在满足\(a[k]+p \le a[i]+j,p \le j,k<i\)的情况下找到一个最大的\(dp[k][p]+1\)并更新当前状态,然后再用这个状态更新之后的状态,有些像线段树的单点查询与修改,但是仅仅是这两个操作我们就不一定要用线段树了,树状数组也可以实现,而且无论在码长还是在常数上都比线段树优秀,所以最终选用树状数组优化

考虑到这题有两个限制条件,即需要满足\(a[k]+p \le a[i]+j\)\(p \le j\)(剩下的一个限制条件在处理时已经被考虑到了,毕竟是从\(1-n\)循环的,所以在查询\((i,j)\)的值时,一定是在\(1...i-1\)的范围内查询、转移的),所以考虑开一个二维的树状数组。二维的树状数组与一维相差不大,我们可以这样理解:例如开一个\(n*n\)的二维的树状数组,那么我们可以先把它们当成\(n\)个不相关的长度为\(n\)的树状数组,按照一般的方法计算好\(n\)个树状数组,然后把这\(n\)个树状数组再按照一般的方法在对应位置上累加一下,我讲得可能一定不太清楚,所以可以看看这位的CSDN博客,然后我们只要在\((a[i]+j,j+1)\)的范围内查询、修改就可以了,为什么\(j\)需要加一?因为\(j\)可能为零不太好处理......这样的话,复杂度就可以被优化到\(O(nklognlogk)\),于是本题就可以通过了,代码如下:

#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e4+10;
const int M=5e2+10;
int n,k,a[N],tmp,c[N*2][M],ans,maxn;
int lowbit(int x){return x&-x;}
void update(int x,int y,int p){
    for(register int i=x;i<=maxn+k+1;i+=lowbit(i))
        for(register int j=y;j<=k+1;j+=lowbit(j))
            c[i][j]=max(c[i][j],p);
}
int query(int x,int y){
    int sum=0;
    for(register int i=x;i;i-=lowbit(i))
        for(register int j=y;j;j-=lowbit(j))
            sum=max(sum,c[i][j]);
    return sum;
}
int main(){
    scanf("%d%d",&n,&k);
    for(register int i=1;i<=n;i++){scanf("%d",&a[i]);maxn=max(maxn,a[i]);}
    for(register int i=1;i<=n;i++)
        for(register int j=k;j>=0;j--){//倒序处理了解一下,不然正序的话j是递增的,后面的j在查询时会把前面的j刚修改过的值带入计算
            tmp=query(a[i]+j,j+1)+1;
            ans=max(ans,tmp);
            update(a[i]+j,j+1,tmp);}
    printf("%d\n",ans);
    return 0;
}
posted @ 2018-07-06 12:48  ForwardFuture  阅读(266)  评论(0编辑  收藏  举报