This way
题意:
给你长度为n的数组,你每次有两种操作:
l r 选择一个区间,将这个区间的每个位置的值-1
i x 选择一个点i,将这个点的值-x
问你要将这个数组清零最少要几次操作
题解:
数据范围5000,暗示的很明显了。首先考虑二维DP
dp[i][j]表示到了第i个位置,1操作的高度为j的时候的答案最小值(注意是高度而不是次数,因为中间可能有阻断的位置)
那么有三种转移的情况:dp[i-1][k(k<j)],dp[i-1][j],dp[i-1][k(k>j)]
但是每次暴力的转移复杂度是 O ( n 3 ) O(n^3) O(n3),所以需要考虑优化
对于第一种情况,我们需要知到从哪里转移过来,因为我们要加上高度差值,如果去找的话,每次是 O ( n ) O(n) O(n),但是我们从小往大做,每次实时地记录答案+差值的话,就可以 O ( 1 ) O(1) O(1)转移,mi_pre就是干这个的。
第二种情况直接转移
第三种情况我们要注意,只有当a[i]<=n&&a[i-1]>=a[i]的时候才会有效,因为这样的话,就可能可以少掉一次2操作
需要从大到小做一遍,因为如果你从小到大做的话,然后从前面最大的后缀转移过来的话,你不知道那个位置是否比a[i]大。
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+5,inf=1e8;
int dp[N][N],a[N],mi_suf[N];
int main()
{
int n;
scanf("%d",&n);
dp[0][0]=0;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
dp[i][j]=inf;
dp[0][0]=0;
mi_suf[n+1]=inf;
for(int i=n;~i;i--)
mi_suf[i]=min(mi_suf[i+1],dp[0][i]);
for(int i=1;i<=n;i++){
int mi_pre=dp[i-1][0];
for(int j=0;j<=min(n,a[i]);j++){
mi_pre++;
dp[i][j]=mi_pre+(j!=a[i]);
dp[i][j]=min(dp[i][j],dp[i-1][j]+(j!=a[i]));
mi_pre=min(mi_pre,dp[i-1][j]);
}
if(a[i]<=n&&a[i-1]>=a[i]){
mi_pre=mi_suf[a[i]];
for(int j=a[i];~j;j--){
dp[i][j]=min(dp[i][j],mi_pre);
if(mi_pre>dp[i-1][j])
mi_pre=dp[i-1][j]+1;
}
}
mi_suf[n+1]=inf;
for(int j=n;~j;j--)
mi_suf[j]=min(mi_suf[j+1],dp[i][j]);
}
int ans=inf;
for(int i=0;i<=n;i++)ans=min(ans,dp[n][i]);
printf("%d\n",ans);
return 0;
}