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;
}
一步一步,永不停息
原文地址:https://www.cnblogs.com/Willems/p/12359272.html