题目大意:
长度为n的序列A,从中删去恰好k个元素(右边的元素往左边移动),记cnt为新序列中Ai=i的元素个数(即权值与下标相同的元素的个数)。求cnt的最大值。
思路:
一开始肯定是想到dp,f[i][j]为前i个删了j个的最大值,显然状态不怎么好转移,那就设改改想法,设f[i][j]为前i个取了j个。然后得出dp方程j==a[i]的时候加一,不然就是背包问题选或者不选。这时候!!我发现,你把i,j做一个坐标系,就可以把问题转化成一个坐标系上,一些点有点值,然后每次可以向下向右下走。,求走到n,(n-k)的最大权值,然后过程可以线段树优化…我没打线段树优化…
程序:
朴素dp
#include<cstdio>
#include<iostream>
#include<algorithm>
#define N 5005
int n,k,ans;
int a[N],f[N];
using namespace std;
int main(){
freopen("a.in","r",stdin);
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=n-k;j>=1;j--)
if (j==a[i]) f[j]=max(f[j],f[j-1]+1);
else f[j]=max(f[j],f[j-1]);
}
printf("%d",f[n-k]);
}
线段树优化
`#include<cstdio>
#include<iostream>
#include<algorithm>
#define N 100005
using namespace std;
int n,k,ans,o;
int a[N],f[N];
struct tree{int max;}t[N*8];
void insert(int rt,int l,int r,int x,int y){
if (l==r){
t[rt].max=max(t[rt].max,y);
return;
}
int mid=(l+r)/2;
if (x<=mid) insert(rt*2,l,mid,x,y);
else insert(rt*2+1,mid+1,r,x,y);
t[rt].max=max(t[rt*2].max,t[rt*2+1].max);
}
void find(int rt,int l,int r,int x,int y){
if (l==x&&r==y){
o=max(o,t[rt].max);
return;
}
int mid=(l+r)/2;
if (mid<x) find(rt*2+1,mid+1,r,x,y);
else if (mid>=y) find(rt*2,l,mid,x,y);
else {
find(rt*2,l,mid,x,mid);
find(rt*2+1,mid+1,r,mid+1,y);
}
}
int main(){
freopen("a.in","r",stdin);
scanf("%d%d",&n,&k);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++){
o=0;
if (i-a[i]<=0) o=a[0];
int x=max(1,i-a[i]);
find(1,1,n,x,i);
f[i]=o+1;
if (x==0) a[0]=max(a[0],f[i]);
else insert(1,1,n,x,f[i]);
if (n-i>=(n-k)-a[i]) ans=max(ans,f[i]);
}
printf("%d",ans);
}