C2. Skyscrapers (线段树 + 分治 || 单调栈 + 思维)
题目: 传送门
题意: 你需要建 n 栋楼,第 i 栋楼最多能建 m[ i ] 层,然后你需要构造一个数组 a 使得对任意 i 满足 1 <= a[ i ] <= m[ i ], 且不能存在 j < i < k and a[ j ] > a[ i ] < a[ k ],也就是说不能同时存在 j 和 k 使得 a[ j ] > a[ i ] 且 a[ i ] < a[ k ],然后你得使得最后建好的 n 栋楼的总楼层最大,输出 a 数组。
1 <= n <= 500000, 1 <= m[ i ] <= 1e9
题解:
解法一:
首先很容易想到分治的算法,我们先求区间 [ 1, n ] 里的最小值的位置,那么根据题意的原则,要么这个最小值的左边都得变成这个最小值, 要么右边都得变成这个值。那我们就可以分治分别求出左边跟右边,然后取最优的即可。然后区间最小值可以用线段树维护,这样最终的复杂度就是 nlogn。
解法二:
我们可以定义 pres[ i ] 为区间 [ 1, i ] 能建总楼层的最大值。然后,我们考虑怎么求这个数组。若 a[ j ] 是 1 ~ i - 1 最后一个小于等于 a[ i ] 的数,那么 j + 1 ~ i 这些楼最多只能建 a[ i ],那么 pres[ i ] = pre[ j ] + (j - i ) * a[ i ]。然后,关于求 1 ~ i - 1 最后一个小于等于 a[ i ] 的数可以用单调栈维护。然后再定义 nxs[ i ] 为区间 [ i, n ] 能建总楼层的最大值。求法和 pres 数组一样。然后,还要维护一下,那些比 a[ i ] 小的数的位置,具体可看代码。那么最后就是,遍历一遍,求最大值 pres[ i ] + nxs[ i ] - a[ i ] 的位置,然后算答案即可。
解法一
#include <bits/stdc++.h> #define LL long long #define mem(i, j) memset(i, j, sizeof(i)) #define rep(i, j, k) for(int i = j; i <= k; i++) #define dep(i, j, k) for(int i = k; i >= j; i--) #define pb push_back #define make make_pair #define INF INT_MAX #define inf LLONG_MAX #define PI acos(-1) using namespace std; const int N = 1e6 + 5; int a[N], ans[N]; int n; pair < int, int > t[N << 2]; void build(int rt, int l, int r) { if(l == r) { t[rt] = make(a[l], l); return ; } int mid = (l + r) >> 1; build(rt << 1, l, mid); build(rt << 1 | 1, mid + 1, r); if(t[rt << 1].first <= t[rt << 1 | 1].first) t[rt] = t[rt << 1]; else t[rt] = t[rt << 1 | 1]; } pair < int, int > query(int rt, int l, int r, int L, int R) { if(L <= l && r <= R) return t[rt]; int mid = (l + r) >> 1; pair < int, int > ans = make(INF, 0); if(L <= mid) { pair < int, int > tmp = query(rt << 1, l, mid, L, R); if(tmp.first < ans.first) ans = tmp; } if(R > mid) { pair < int, int > tmp = query(rt << 1 | 1, mid + 1, r, L, R); if(tmp.first < ans.first) ans = tmp; } return ans; } 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(1, 1, n, l, r); 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]; // cout << ans1 << " " << ans2 << " " << pos << endl; if(ans1 >= ans2) { rep(i, pos, r) ans[i] = tmp.first; return ans1; } else { rep(i, l, pos) ans[i] = tmp.first; return ans2; } } int main() { scanf("%d", &n); rep(i, 1, n) scanf("%d", &a[i]); build(1, 1, n); solve(1, n); rep(i, 1, n) printf("%d ", ans[i]); return 0; }
解法二
#include <bits/stdc++.h> #define LL long long #define mem(i, j) memset(i, j, sizeof(i)) #define rep(i, j, k) for(int i = j; i <= k; i++) #define dep(i, j, k) for(int i = k; i >= j; i--) #define pb push_back #define make make_pair #define INF INT_MAX #define inf LLONG_MAX #define PI acos(-1) using namespace std; const int N = 1e6 + 5; int a[N], pre[N], nx[N], s[N], tot, ans[N]; LL pres[N], nxs[N]; int main() { int n; scanf("%d", &n); rep(i, 1, n) scanf("%d", &a[i]); rep(i, 1, n) { while(tot && a[i] < a[s[tot]]) tot--; pre[i] = s[tot]; pres[i] = pres[s[tot]] + 1LL * (i - s[tot]) * a[i]; s[++tot] = i; } tot = 0; s[0] = n + 1; dep(i, 1, n) { while(tot && a[i] < a[s[tot]]) tot--; nx[i] = s[tot]; nxs[i] = nxs[s[tot]] + 1LL * (s[tot] - i) * a[i]; s[++tot] = i; } LL ma = 0LL; int pos = -1; rep(i, 1, n) { if(pres[i] + nxs[i] - 1LL * a[i] > ma) { ma = pres[i] + nxs[i] - 1LL * a[i]; pos = i; } } for(int i = pos; i; i = pre[i]) { rep(j, pre[i] + 1, i) ans[j] = a[i]; } for(int i = pos; i <= n ; i = nx[i]) { rep(j, i, nx[i] - 1) ans[j] = a[i]; } rep(i, 1, n) printf("%d ", ans[i]); return 0; }