【2022牛客多校9】I-The Great Wall II (dp, 单调栈, 思维)

题目

https://ac.nowcoder.com/acm/contest/33194/I

在这里插入图片描述
在这里插入图片描述

思路

  • 题目大意就是,给一个长为 n 的序列,要把它分成 k 段连续序列,每一段的代价是这一段中的最大值。分别对 k=1, 2, …, n,问最少代价。
  • f[i][j] 代表前 i 个数,分成 j 段的最少代价。那么会有如下一个朴素的动态规划:
  • f [ i , j ] = min ⁡ 1 ≤ x < i { f [ x , j − 1 ] + max ⁡ x ≤ k ≤ i a [ k ] } f[i, j] = \min_{1 \le x < i} \{ f[x, j-1] + \max_{x \le k \le i}a[k] \} f[i,j]=1x<imin{f[x,j1]+xkimaxa[k]}
  • 上面这个可以说是 O(n^3) 的,考虑如何优化。
  • 对于 dp[i][j] 我们考虑序列中在 a[i] 左边第一个大于 a[i] 的数 a[t],那么在上面的递推式中,
    • x > t 时,右半部分 max 取值都为 a[i],最优就变成这个区间内的 f 最小值;(相当于最右边那一段是以 a[i] 为最高的)
    • x <= t 时,其实就是 f[t, j]。 (相当于 a[i] 被原来的最右边那一段吸收了)
  • x>t 那部分,可以用单调栈维护左边第一个大于它的位置以及该区间内的 f 最小值。
  • 总的时间复杂度是 O(n^2)

在这里插入图片描述

代码

#include <bits/stdc++.h>
using namespace std;

#define MAXN (8005)

const int INF = 1000000000;

int n;
int a[MAXN];
int f[MAXN][MAXN];
int tmp;


void solve()
{
    scanf("%d", &n);
    for (int i=1; i<=n; i++) scanf("%d", &a[i]);
    a[0] = INF;
    for (int i=0; i<=n; i++) for (int j=0; j<=n; j++) f[i][j]=INF;

    f[0][1]=0;
    for (int i=1; i<=n; i++) f[i][1]=max(f[i-1][1],a[i]);

    for (int j=2; j<=n; j++) {
        stack<pair<int, int>> st;
        //f[j][j]=f[j-1][j-1]+a[j];
        st.push({f[j-1][j-1], 0});
        st.push({INF, j-1});
        for (int i=j; i<=n; i++) {
            tmp=INF;
            while (!st.empty() && a[st.top().second]<=a[i]) {
                auto o = st.top();
                st.pop();
                tmp = min(tmp, o.first);
            }
            auto o = st.top();
            st.pop();
            tmp = min(tmp, o.first);

            f[i][j] = min(f[o.second][j], tmp+a[i]);
            f[i][j] = min(f[i][j], f[i-1][j-1] + a[i]);

            o.first = min(tmp, f[i][j-1]);
            st.push(o);
            st.push({INF, i});
        }
    }

    for (int i=1; i<=n; i++) {
        printf("%d\n", f[n][i]);
    }
}


int main()
{
    //freopen("1.txt", "r", stdin);
    solve();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值