CodeForces

题目链接

题目大意

  给n个数,你可以使每个数的值减小(最小减到1),问把这n个数改成没有左右两个数都大于中间数的情况最少需要减去多少。

解题思路

  先考虑暴力解法,枚举每个数作为最大的那个数,然后分别从左右两个方向开始,如果当前的数字大于之前的数字,就将他改成之前的那个数。
  然后我们可以考虑dp来做,(dp[i])表示以i为最大值的时候需要减去的最少的数(左右都要进行一次,这里只讲左边),如果这一列数使非严格单调递增的话,代价自然是0,否则的话,如果当前位置i的数小于之前的数,因为对于前面的所有方案,都是非严格单调递增的,所以只要找到一个前面一个位置j,满足位置j上的数小于当前的数,然后让他们之间的数都改成位置i上的数就行了。当然如果这样直接暴力查询的话必定超时,我们用单调栈来代替之前的查询,只要当前的数字比栈顶的数字小,就把栈顶弹出,新的栈顶就是比当前数字小的值了。左右都跑一遍,然后枚举中间点就行了。

代码

const int maxn = 5e5+10;
int n, pos, arr[maxn]; ll pre[maxn], post[maxn];
int main() {
    cin >> n;
    for (int i = 1; i<=n; ++i) scanf("%d", &arr[i]);
    stack<P> sk; ll sum = 0;
    for (int i = 1; i<=n; ++i) {
        int w = 1;
        while(!sk.empty() && sk.top().x>arr[i]) {
            sum -= 1LL*sk.top().x*sk.top().y;
            w += sk.top().y, sk.pop();
        }
        //cout << w << endl;
        sk.push({arr[i], w});
        sum += 1LL*arr[i]*w;
        pre[i] = sum;
    }
    while(!sk.empty()) sk.pop();
    sum = 0;
    for (int i = n; i>=1; --i) {
        int w = 1;
        while(!sk.empty() && sk.top().x>arr[i]) {
            sum -= 1LL*sk.top().x*sk.top().y;
            w += sk.top().y, sk.pop();
        }
        sk.push({arr[i], w});
        sum += 1LL*arr[i]*w;
        post[i] = sum;
    }
    //for (int i = 1; i<=n; ++i) cout << pre[i] << ' ' << post[i] << endl;
    ll ans = 0;
    for (int i = 1; i<=n; ++i) {
        ll t = pre[i]+post[i]-arr[i];
        if (t>ans) {
            ans = t;
            pos = i;
        }
    }
    int t = arr[pos];
    for (int i = pos; i>=1; --i) {
        if (t>arr[i]) t = arr[i];
        arr[i] = t;
    }
    t = arr[pos];
    for (int i = pos; i<=n; ++i) {
        if (t>arr[i]) t = arr[i];
        arr[i] = t;
    }
    for (int i = 1; i<=n; ++i) printf(i==n ? "%d
":"%d ", arr[i]);
    return 0;  
}
原文地址:https://www.cnblogs.com/shuitiangong/p/14551768.html