CodeForces - 1313C2 Skyscrapers (hard version)(单调栈)
题目大意
给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\n":"%d ", arr[i]);
return 0;
}