【洛谷】P3287 [SCOI2014]方伯伯的玉米田(线性dp+数据结构优化)
题意
给定一个长度为 \(n\) 的序列 \(a\),最多可以执行 \(k\) 次将一个区间 \([l,r]\) 中的数每一个数都增加 \(1\),求最终可以得到的最长不下降子序列的长度最大值。
数据范围
\(1 <n \leq 10000,1 \leq k \leq 500,1 \leq a_i \leq 5000\)。
思路
首先不难发现本题中的一个性质,每一次修改操作的右端点 \(r=n\)。这是因为每次都把右边的数都 \(+1\),并不会导致答案的长度减小。而如果把右边的数都增加了,那么这个子序列就有可能接到前面的一个子序列后面,就会使答案变大。故每次右端点都选 \(n\) 不会使答案减小。
设 \(f[i][j]\) 表示以 \(i\) 结尾的使用了 \(j\) 次修改(每次修改的 \(l \leq i,r=n\))的最长不下降子序列的长度。考虑枚举将 \(i\) 接到前面的子序列后面,就可以得到朴素的状态转移方程:
\(f[i][j]=max(f[i][j],f[x][y])\),其中 \(x \leq i,y\leq j\),同时也需要满足 \((a[j]+y \leq a[i]+j)\) (有\(j-y\) 次操作以 \(i\) 为左端点,区间中不包括 \(j\))。
这个转移的时间复杂度是 \(O(n^2k^2)\) 的,非常需要优化。
注意到转移中是取最大值,那么就可以考虑用一种数据结构来记录区间内的最大值。
由于 \(i\) 顺序枚举,所以 \(x \leq i\) 可以直接满足。而对于额外的高度要求,可以看成是以 \(a[j]+y\) 结尾的子序列。那么就只需要快速查询出所有满足 \(a[j]+y \leq a[i]+j,y \leq j\) 中的最大值。
由于所有操作都是单点修改,可以考虑用二维树状数组来实现(具体实现方法看代码比较好理解)。那么时间复杂度就可以降低到 \(O(n k \log nk)\)。可以通过本题。
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10010;
const int M=5550;
const int K=521;
const int INF=0x3f3f3f3f;
int f[N][K],n,k,a[N],c[M][K];//第一维表示最右边的高度,第二维表示修改次数
int lowbit(int x){return x&(-x);}
void updata(int x,int y,int val)
{
for(int i=x;i<=5500;i+=lowbit(i)) //枚举到最大可能高度
for(int j=y;j<=k+1;j+=lowbit(j)) //由于修改次数可能为0,所以需要将次数+1才能用树状数组
c[i][j]=max(c[i][j],val);
}
int query(int x,int y)
{
int res=0;
for(int i=x;i;i-=lowbit(i))
for(int j=y;j;j-=lowbit(j))
res=max(res,c[i][j]);
return res;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
for(int j=k;j>=0;j--)
{
f[i][j]=query(a[i]+j,j+1)+1;//这两行的+1都是为了防止修改0次而死循环
updata(a[i]+j,j+1,f[i][j]);//表示更新一个修改了j次,右端点为a[i]+j,长度为 f[i][j] 的序列
}
printf("%d\n",query(5500,k+1));//最后的答案是所有可能答案的最大值,不一定以最后一数为右端点
return 0;
}

浙公网安备 33010602011771号