3780 构造数组(单调栈)

本文介绍了一种算法思路,如何通过预处理构建一个整数数组,使其满足特定条件:每个元素在给定范围内,且不存在连续子数组违反单调性。通过维护非严格单调递增和递减序列的最大和,找到最优的峰值位置,实现最大总和的数组构造。适合解决单峰函数问题和数组优化问题。
摘要由CSDN通过智能技术生成

1. 问题描述:

给定一个长度为 n 的整数数组 m1,m2,…,mn。现在,请你构造一个数组 a1,a2,…,an。对于构造的数组,有以下三点要求:
∀i∈[1,n],1 ≤ ai ≤ mi 成立。
∀i∈[1,n], 不存在数对 j,k 同时满足 j < i < k 且 aj > ai < ak。
数组中所有元素之和尽可能大。请输出任意合理方案。

输入格式

第一行包含整数 n。第二行包含 n 个整数 m1,m2,…,mn。

输出格式

输出 n 个整数,表示你构造出的数组 a1,a2,…,an。如果答案不唯一, 输出任意合理方案均可。

数据范围

前三个测试点满足 1 ≤ n ≤ 10。
全部测试点满足 1 ≤ n ≤ 5 × 10 ^ 5,1 ≤ mi ≤ 10 ^ 9。

输入样例1:

5
1 2 3 2 1

输出样例1:

1 2 3 2 1

输入样例2:

3
10 6 8

输出样例2:

10 6 6
来源:https://www.acwing.com/problem/content/description/3783/

2. 思路分析:

根据第二个条件可以知道不可能左右两边同时存在比它大的数字,画画图可以知道实际上我们需要求解的是一个单峰函数(只要存在两个峰值那么一定是不满足题目要求的,中间部分一定会出现题目中不满足要求的部分),可以发现单峰函数一定是可以满足题目要求的,所以我们可以枚举出哪一个点k作为峰值使得k的左边是非严格单调递增,k的右边是非严格单调递减的,也即将原数组变成左边是非严格单调递增,右边是非严格单调递减,由于左右两边是独立的所以我们可以分开处理,只看其中一边即可,我们可以用l和r分别存储所有点作为峰值对应非严格单调递增和非严格单调递减的最大总和,其中l[i]表示从1到i非严格单调递增的最大总和,r[i]表示i到n非严格单调递减的最大总和,所以问题就转化为了如何预处理出l和r的值,我们可以先看l如何求解,由于l和r是对称的所以使用相同的方法求解即可。对于当前位置i作为峰值,如果上一个位置i - 1的值w[i - 1] > w[i]那么上一个位置的值最大为w[i - 1],我们可以一直往前找,所有大于等于w[i]的值都需要设置为w[i]这样才可以满足以i作为峰值非严格单调递增而且总和是最大的要求,找到第一个比w[i]小的位置说明此时就结束了,这个位置为t,由于是从前往后计算维护l的值所以位置t之前的所有值都小于等于w[i],对应的最大总和为l[t] + (i - t) * w[i],这样从前往后枚举就可以得到所有的l[i],在枚举的过程中需要找到左边第一个比当前位置小的元素的位置,而找左边第一个比当前元素小的位置可以使用单调栈来解决,在找左边非严格单点递增的时候需要设置一个哨兵0,这样可以避免边界上的问题,而且计算总和的时候也非常方便,在枚举的时候维护l的值即可;对于r的求解也是类似的,我们可以枚举出每一个位置作为峰值右边维护非严格单调递减的最大总和,可以从后往前枚举找到右边第一个比当前位置元素小的位置即可,最后枚举一下哪一个位置k作为峰值可以取到最大的总和,然后从k这个位置开始从后往前枚举依次确定每一个w[i]的值,对于k的左边w[i] = min(w[i],w[i + 1]),对于k右边也是类似的但是需要注意一个问题是第k + 1个位置的元素是确定的,虽然说k可以作为峰值但是k + 1的位置的元素是可以大于k 位置的元素的,只需要满足k左边是非严格单调递增,右边非严格单调递减即可:

3. 代码如下:

class Solution:
    def process(self):
        n = int(input())
        # w前面加上一个0这样w元素下标可以从1开始
        w = [0] + list(map(int, input().split()))
        stack, l, r = [0] * (n + 10), [0] * (n + 10), [0] * (n + 10)
        # 0是一个哨兵, tt是栈顶的一个指针
        tt = 0
        # stack[0] = 0这样可以避免边界问题后面计算总和的时候也可以避免边界问题, stack存储的是下标
        # 求解当前点i作为峰值左边非单调递增的总和
        for i in range(1, n + 1):
            while tt and w[stack[tt]] >= w[i]: tt -= 1
            l[i] = l[stack[tt]] + (i - stack[tt]) * w[i]
            tt += 1
            stack[tt] = i
        tt = 0
        # 与左边是对称的, 求解当前点作为峰值右边非单调递减的总和
        stack[0] = n + 1
        for i in range(n, 0, -1):
            while tt and w[stack[tt]] >= w[i]: tt -= 1
            r[i] = r[stack[tt]] + (stack[tt] - i) * w[i]
            tt += 1
            stack[tt] = i
        res = k = 0
        # 求解哪一个点作为峰值可以得到最大的总和
        for i in range(1, n + 1):
            t = l[i] + r[i + 1]
            if t > res:
                res, k = t, i
        for i in range(k - 1, 0, -1):
            w[i] = min(w[i], w[i + 1])
        # 注意位置为k + 1的点是确定的需要从k + 2开始
        for i in range(k + 2, n + 1):
            w[i] = min(w[i], w[i - 1])
        for i in range(1, n + 1):
            print(w[i], end=" ")


if __name__ == '__main__':
    Solution().process()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值