C.Skyscrapers

Posted Willems

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C.Skyscrapers相关的知识,希望对你有一定的参考价值。

题目大意

现在要建 n 栋楼,有两个限制:

限制一:第 i 栋楼最多只能建 m[i] 层;

限制二:假设现在第 i 栋楼建了 a[i] 层,那么数组 a 需要满足对于任意的 i,都满足 1 <= a[i] <= m[i],且不能存在 j < i < k 使得 a[i] < a[j] && a[i] < a[k]

要求你最终建好的总楼层数尽可能的大,即 a[1] + ... + a[n] 尽可能大,输出数组 a。

 

方法一

假设我们当前在处理区间 [l, r] 内的建楼问题,并且数组 m 在区间 [l, r] 中的最小值出现在下标为 pos 的位置,我们应该让这个位置的楼层尽可能的高,所以 a[pos] = m[pos];

为了让区间 [l, r] 满足限制二,我们要么数组 a 的 [l, pos-1] 整个区间都等于 a[pos],要么让数组 a 的 [pos+1, r] 整个区间都等于 a[pos],那么我们只需要在两种方案中选择最佳的那个即可。

于是,我们可以用分治来解决这个问题,一开始处理的是区间 [1,n],然后找到最小值位置 pos,就将区间分成 [1,pos-1] 和 [pos+1,n] 两个区间,再递归的去求解,取最优解即可。

C1:n <= 1000

由于 n <= 1000,我们可以 O(n^2) 做,对于 找每个区间最小值的位置 这一问题,我们可以直接 O(n) 枚举去找,这样,总的复杂度就是 O(n^2)。

 

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

const int N = 1e6 + 5;
int a[N], ans[N];
int n;

pair < int, int > query(int l, int r) { /// 求解区间 [l,r] 的最小值和最小值位置
    int pos = l, mi = a[l];
    for(int i = l; i <= r; i++) {
        if(a[i] < mi) {
            mi = a[i];
            pos = i;
        }
    }
    return make_pair(mi, pos);
}

LL solve(int l, int r) {
    if(l == r) { /// 递归出口
        ans[l] = a[l];
        return 1LL * a[l];
    }
    if(l > r) return 0LL;

    pair < int, int > tmp = query(l, r); /// 找到区间 [l, r] 的最小值和最小值位置,first是最小值,second是最小值的位置

    int pos = tmp.second;
    /// 分治求解两个区间
    LL ans1 = solve(l, pos - 1) + 1LL * (r - pos + 1) * a[pos]; 
    LL ans2 = solve(pos + 1, r) + 1LL * (pos - l + 1) * a[pos];

    /// 取最优解并保存答案
    if(ans1 >= ans2) { 
        for(int i = pos; i <= r; i++) ans[i] = tmp.first;
        return ans1;
    }
    else {
        for(int i = l; i <= pos; i++) ans[i] = tmp.first;
        return ans2;
    }
}

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);

    solve(1, n); /// 分治求解

    for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
    return 0;
}
View Code

 

C2:n <= 500000

这个时候再 O(n^2) 做会超时,对于 找每个区间最小值的位置 这一问题,我们考虑用线段树来维护,找最小值位置只需 O(logn),那么总的复杂度就是 O(nlogn)。

 

方法二

我们可以想象最终的 a 数组一定是类似于那种 “山峰” 的感觉,也就是说最终的 a 数组存在一个最高点,我们假设其下标为 pos,那么数组 a 在区间 [1,pos] 一定是非递减(即 a[1] <= a[2] .... <= a[pos])的,在区间 [pos,n] 一定是非递增的(即 a[pos] >= .... >= a[n])。那么可以枚举这个最高点的位置,确定了一个位置,其他位置就可以贪心的取最大,最后算出总和,维护一个最优解即可。

C1:n <= 1000

由于 n <= 1000,我们可以 O(n^2) 做,先是 O(n) 的枚举最高点的位置,再对于每个最高点,O(n) 的求出其他位置能取到的最大值,并算出总和,维护一个最优解即可。

C2:n <= 500000

这个时候,不能再 O(n^2) 暴力求解了,我们可以假设 pre[i] 为当前 a[i] = m[i] 且数组 a 在区间 [1,i] 非递减的最大总楼层数,然后再设 suc[i] 为当前 a[i] = m[i] 且数组 a 在区间 [i,n] 非递增的最大总楼层数,那么以 i 作为最高点能建的最大总楼层数为 pre[i] + suc[i] - m[i]。那么如果我们可以先预处理出 pre[i] 和 suc[i],我们就只需 O(n) 的枚举最高点,维护一个 pre[i] + suc[i] - m[i] 的最大值以及取这个最大的位置 pos,最终根据这个 pos 去生成数组 a 即可。

那么 pre[i] 和 suc[i] 要怎么预处理呢。

对于 pre[i],因为此时 a[i]=m[i],那么如果 m[i-1] >= a[i],a[i-1] 最多也只能取 a[i],但是如果 a[i-1] <= m[i],那么 a[i-1] 可以取到 m[i-1],那既然 a[i-1] 可以取到 m[i-1] 了,对于区间 [1,i-1] 我们可以直接继承 pre[i-1] 就行了,因为我们要求 pre[i],我们的 pre[1],pre[2]....pre[i] 肯定都是已经算好了。所以,对于 pre[i] 我们只需找到区间 [1,i-1] 中最后一个小于等于 m[i] 的位置 pos,那么 pre[i] = pre[pos] + (i-pos)*m[i]。

此时还有一个问题就是,怎么快速找到区间 [1,i-1] 中最后一个小于等于 m[i] 的位置 pos 呢,这个问题,可以用单调栈来求得。

对于 suc[i],也是同样的道理,这次我们先求 suc[n],再求 suc[n-1],那么对于 suc[i] 我们找到区间 [i+1,n] 中第一个小于等于 m[i] 的位置 pos,那么 suc[i] = suc[pos] + (pos-i)*m[i],依然用单调栈来找区间 [i+1,n] 中第一个小于等于 m[i] 的位置 pos。

那么总的复杂度是 O(n) 的。

 

以上是关于C.Skyscrapers的主要内容,如果未能解决你的问题,请参考以下文章

微信小程序代码片段

VSCode自定义代码片段——CSS选择器

谷歌浏览器调试jsp 引入代码片段,如何调试代码片段中的js

片段和活动之间的核心区别是啥?哪些代码可以写成片段?

VSCode自定义代码片段——.vue文件的模板

VSCode自定义代码片段6——CSS选择器