补墙——单调栈

补墙(wall)
【题目描述】
小R最近在玩一款塔防类游戏。在游戏中,小R修了一排城墙来保护他的基地。在敌人的一轮进攻之后,城墙的许多地方被破坏了,变得参差不齐。小R是一个强迫症患者,他看到这些参差不齐的城墙觉得非常难受,因此决定对这些城墙进行修补。城墙共分为n段,从左到右排成一排,第i段城墙的高度为h_i。相邻两段城墙的高度差距越大,小R看着就越难受,因此他定义整排城墙的混乱程度为相邻两段的高度差之和。需要注意的是,第一段城墙与最后一段城墙都与地面相邻(地面的高度为0)。例如,有一排城墙的高度分别为3,7,2,则这排城墙的混乱程度为3+4+5+2=14。现在小R希望通过使某些段城墙变高的方式降低城墙的总混乱程度,他将一段城墙的高度变高1需要花费1枚金币,现在他希望让总混乱程度小于等于k,请问至少要花费多少金币。


【输入格式】
第一行两个正整数n,k。
第二行n个正整数,第i个正整数表示h_i。


【输出格式】
输出一行一个正整数,表示至少花费的金币。如果无论花费多少金币都无法使得总混乱程度小于等于k,则输出-1。


【输入样例】
5 10
4 2 1 1 3


【输出样例】
2


样例解释
修补后的城墙高度分别为4 2 2 2 3。


【数据范围与约定】
对于20%的数据,n≤10,h_i≤100
对于40%的数据,n≤50,h_i,k≤100
对于60%的数据,n≤50,h_i,k≤500

对于100%的数据,n≤500000,h_i≤〖10〗^9,k≤〖10〗^18


解析:

        这是一道不容易想到单调栈的题,但我们可以想到,任意一段墙,当它增高时,只有当左右两段都高于它时,不整齐值才会减小,且每增高一单位,不整齐值减2。

于是,我们试想求出满足上述条件的几段墙:根据中间低于两边以及O(N)才能过的范围,我们想到利用单调性来解决。

  我们使用一个单调递减的单调栈,栈内任意一段墙保证:它左边的一段的高度一定大于它。当当前加入的值大于栈顶,则栈顶的前一个元素、栈顶、当前值满足了上述条件,于是将其记录下来,同时维护栈的单调性。最后将记录的元素排序后求出答案(排序可用快排、堆排,个人认为还可使用桶排序,但在极端数据下有超内存的风险)。


代码:

#include <cstdio>
#include <algorithm>
#include <queue>
#include <iostream>


long long read()
{
char last = '+', ch = getchar();
while (ch < '0' || ch > '9') last = ch, ch = getchar();
long long tmp = 0;
while (ch >= '0' && ch <= '9') tmp = tmp * 10 + ch - 48, ch = getchar();
if (last == '-') tmp = -tmp;
return tmp;
}


std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int> >, std::greater<std::pair<int, int> > > Q;
int n;
long long k;
long long now;
long long x[600000];
int last;
int l[600000];
int r[600000];
int s[600000];
long long v[600000];
long long ans;


long long abss(long long x)
{
if (x < 0)
{
return -x;
}
return x;
}


int main()
{
freopen("wall.in", "r", stdin);
freopen("wall.out", "w", stdout);
n = read();
k = read();
now = 0;
for (int i = 1; i <= n; i++)
{
x[i] = read();
}
for (int i = 1; i <= n + 1; i++)
{
now += abss(x[i] - x[i - 1]);
}
last = 0;
for (int i = 1; i <= n; i++)
{
if (x[i] != x[i - 1])
{
last++;
v[last] = x[i];
s[last] = 1;
}
else
{
s[last]++;
}
}
for (int i = 1; i <= last; i++)
{
l[i] = i - 1;
r[i] = i + 1;
}
for (int i = 1; i <= last; i++)
{
if (v[i] < v[i - 1] && v[i] < v[i + 1])
{
Q.push(std::make_pair(s[i], i));
}
}
ans = 0;
while (now > k && ! Q.empty())
{
int u = Q.top().second;
Q.pop();
long long ttt = std::min(v[l[u]], v[r[u]]) - v[u];
if (now - ttt * 2 > k)
{
now = now - ttt * 2;
ans += ttt * s[u];
v[u] = v[u] + ttt;
if (v[u] == v[l[u]])
{
s[u] += s[l[u]];
l[u] = l[l[u]];
r[l[u]] = u;
}
if (v[u] == v[r[u]])
{
s[u] += s[r[u]];
r[u] = r[r[u]];
l[r[u]] = u;
}
if (v[u] < v[l[u]] && v[u] < v[r[u]])
{
Q.push(std::make_pair(s[u], u));
}
}
else
{
ans += (now - k + 1) / 2 * s[u];
now = k;
}
}
if (now <= k)
{
std::cout << ans << std::endl;
}
else
{
std::cout << -1 << std::endl;
}
return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值