[SCOI2014]方伯伯的玉米田
首先一个很显然的事实,我们所有操作的区间的右端点都是\(n\),即我们操作的区间是一些后缀
这个很好理解啊,我们使得一个区间整体增加是为了尽可能和前面的点形成不降子序列,并没有必要仅提升一段区间的高度使得这一段难以和后面的接上,所以直接操作到\(n\)一定是最优的。
设\(dp_{i,j}\)表示前\(i\)个后缀操作共操作了\(j\)次,显然有转移
\[dp_{i,j}=\max_{k<j,p\leq j}1+dp_{k,j-p}[a_k\leq a_i+p]
\]
就是枚举一下从点\(k\)转移过来,对于\(i\)这个后缀使用了\(p\)次操作,这样\(k,i\)的相对高度差就增加了\(p\)
这个转移是\(O(n^2k^2)\)的,显然啥都过不去
不妨仔细分析一下这个转移
当\(a_k\leq a_i\)的时候,由于\(dp\)数组有单调性,即操作次数越多形成的不降序列就越长,所以转移肯定是\(dp_{i,j}=\max_{k<j} 1+dp_{k,j}\),我们可以维护\(k\)个树状数组,这个转移只需要去第\(j\)个树状数组里查下标不超过\(a_i\)的最大值即可。
当\(a_k>a_i\)的时候,还是根据\(dp\)数组的单调性,转移肯定是\(dp_{i,j}=\max_{k<j}1+dp_{k,j-a_k+a_i}\),换言之,如果\(dp_{k,p}\)能向\(dp_{i,j}\)进行转移,那么必定会满足\(a_k-a_i=j-p\),即\(a_k+p=a_i+j\)。我们还是维护多个树状数组,去第\(a_i+j\)个树状数组里查下标不超过\(j\)的,这样对应的\(a_k\)就肯定会大于\(a_i\)。当然加入\(dp_{i,j}\)的时候要加到第\(a_i+j\)个树状数组的第\(j\)个位置。
复杂度就是\(O(nk\log a_ik)\)
代码
#include<bits/stdc++.h>
#define re register
#define lb(x) ((x)&(-x))
#define max(a,b) ((a)>(b)?(a):(b))
inline int read() {
char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
const int maxn=1e4+5;
int n,k,a[maxn],M;
int c[505][5005],d[5505][505],dp[maxn][505];
inline int qmax(int id,int x) {
int nw=0;
for(re int i=x;i;i-=lb(i)) nw=max(c[id][i],nw);
return nw;
}
inline void add(int id,int x,int v) {
for(re int i=x;i<=M;i+=lb(i)) c[id][i]=max(c[id][i],v);
}
inline int Qmax(int id,int x) {
int nw=0;
for(re int i=x;i;i-=lb(i)) nw=max(nw,d[id][i]);
return nw;
}
inline void Add(int id,int x,int v) {
for(re int i=x;i<=k+1;i+=lb(i)) d[id][i]=max(d[id][i],v);
}
int main() {
n=read(),k=read();
for(re int i=1;i<=n;i++) a[i]=read(),M=max(a[i],M);
for(re int i=1;i<=n;i++)
for(re int j=0;j<=k;j++) {
dp[i][j]=qmax(j,a[i])+1;
int h=Qmax(a[i]+j,j+1)+1;
dp[i][j]=max(dp[i][j],h);
if(j) dp[i][j]=max(dp[i][j-1],dp[i][j]);
add(j,a[i],dp[i][j]);
Add(a[i]+j,j+1,dp[i][j]);
}
int ans=0;
for(re int i=1;i<=n;i++) ans=max(ans,dp[i][k]);
printf("%d\n",ans);
return 0;
}