问题 K: 实在是太美
题目描述
艾斯洛克希望你送给她一个长度为N的合法括号序列,保证N是偶数。你在序列的第Ai位放左括号的代价为Bi,放右括号的代价为 。艾斯洛克不想让你太麻烦,所以希望你支付最小的代价。不过才不是担心你呢!真的不是哦!虽然众所周知,但艾斯洛克还是给了你合法括号序列的定义,以防你这个八嘎会错意了:
·空序列是合法括号序列;
·如果A是合法括号序列,那么(A)是合法括号序列;
·如果A,B,都是合法括号序列,那么AB是合法括号序列。
「知…… 知道了的话就快去准备吧!虽然我也不是很期待就是了!!」艾斯洛克说。
输入
第一行输入一个正整数N。
第二行输入N个整数表示Ai。
第三行输入N个整数表示Bi。
输出
输出一个整数表示答案。
样例输入 [Copy](javascript:CopyToClipboard($(‘#sampleinput’).text()))
6
8 2 -2 -5 4 1
4 4 1 -3 6 6
样例输出 [Copy](javascript:CopyToClipboard($(‘#sampleoutput’).text()))
17
提示
样例解释:最小代价的方案为 ((())) 或 ()()(),答案均为17,不存在答案更小的合法括号序列。

思路
这道题目我是DP去做,可是会超时,所以当时就没有其他的办法。
看到题解是反悔贪心,我就去复习了一下,刚看完反悔堆,就想着这个题目能不能也用这个解决,可是我发现,我根本不会维护括号序列的合法性,尤其还是贪心的条件下,一点思路都没有。
看了题解才发现,确实用的是反悔堆,维护合法性的思路非常奇妙。
每次都是将成对的括号对答案做出贡献,如果当前这对括号换成两个右括号,并且将以前的某一对括号换成两个左括号的贡献比当前更少,那么我们有限采用后者。
那么怎么实现呢,我们可以用堆来维护每一个右括号变成左括号的代价,然后每次新加入一对括号时,取出堆顶判断,哪边更优,选择哪边。
这样贪心可以保证序列合法化的原因是:任何一个合法序列,调整任何一个右括号变成左括号,此时 再在最后加入两个右括号,总能合法。
其实对于这个题目,我仍然有个思考,那就是:反悔贪心的思想是打破贪心原有的局部最优解情况,通过反悔操作保证全局最优解。这句话让人想到的不是反悔贪心,而是DP,DP就是保证了全局最优解,虽然也是从局部最优解转移而来,但是此时的二者是截然不同的,因为,DP可以通过状态设计,有效的保证任何情况下的局部最优解,我们可以理解为是有限制的局部最优解,因此保证了全局最优解的特性。
但是,贪心如何保证做到全局最优解?仅仅还是通过一个反悔操作?
我通过一个样例貌似理解了:
原状态的序列
()()()()
我们按照程序跑,假如说跑到第三组括号时,发生了如下修改,是当前最优解
((()))
我加入第四组括号的左括号特别特别大,此时反悔堆中有优先弹出最小的右括号,此时4,5,6位置都是右括号,我们来考虑一下情况,如果4,5其中一个变为左括号,我们发现,其实和这个左括号匹配的并不是第四组的右括号,而是6号位的右括号,那么我们的目的是是什么呢,是让曾经与4,5之中匹配的左括号和第四组的右括号匹配。
假如说,全局最优解就是这个状态
((()()))
言外之意就是,前三组括号时不应该进行更改变成 ((())),但是我们发现,通过跑程序,即使在仅有前三组的时候,确实进行了更改,将序列变成 ((())),但是最后将5号位的右括号又变成了左括号,相当于复原了保持了最优子状态,从而得到了最优解。
说的有点繁琐,简单的讲:或许这个反悔确实有点东西,确实,贪心的局部最优解性质仍然存在,但是因为这个反悔操作,改变了已经存在的最优子结构,从而保证了全局最优解。有点抽象,感受感受吧
ACM的尽头是玄学
/*
太神奇了,居然能够通过贪心保证序列的合法性
*/
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read(){int x;scanf("%lld",&x);return x;}
const int B=1e6+10;
int T;
int n;
int a[B];
int b[B];
priority_queue<int,vector<int>,greater<int>>q;
void work()
{
cin>>n;
for (int i=1;i<=n;i++) a[i]=read();
for (int i=1;i<=n;i++) b[i]=read();
q.push({a[2]-b[2]});
int sum=a[1]+b[2];
for (int i=3;i<=n;i+=2)
{
if (q.top()+b[i]+b[i+1]<a[i]+b[i+1])
{
sum+=q.top()+b[i]+b[i+1];
q.pop();
q.push(a[i]-b[i]);
q.push(a[i+1]-b[i+1]);
}
else
{
sum+=a[i]+b[i+1];
q.push(a[i+1]-b[i+1]);
}
}
cout<<sum;
}
signed main()
{
T=1;
while (T--) work();
return 0;
}
6万+

被折叠的 条评论
为什么被折叠?



