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; }
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的主要内容,如果未能解决你的问题,请参考以下文章