Codeforces 1400 E. Clear the Multiset —— 暴力一眼DP

202 篇文章 6 订阅

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值