Description
給定一個由 ()?
構成的序列,其中 ?
可以換成左括號和右括號。每一個 ?
替換成兩個括號都有對應的代價,求讓序列爲括號匹配序列的替換最小代價和方案。
Solution
有許多做法,暴力就是 dp。大概是 爲前 個字符,左括號與右括號之差爲 的最小代價。顯然轉移是 。還是那句話——dp 不行考慮貪心。
可以將所有的括號和問號都當成 ?
來處理,那麼 (
變成右括號的代價爲正無窮,)
變成左括號的代價也是正無窮,自己變自己沒有花費。默認所有的 ?
都是右括號,需要的時候取出最優的 ?
變成左括號,可以維護變成兩個括號的花費之差用堆維護。
如果考場上想不到那麼清新的做法呢。按自己想法來,把左括號看作 ,右括號爲 ,做一遍前綴和。如果有 ,那麼原序列爲合法括號匹配序列。
遍歷一次原序列,把 ?
變成花費小的,同時做前綴和。維護兩個堆 和 。 的堆首爲堆中的右括號中可以變成左括號多出的花費差最小的, 同理,並且 和 全是 ? 變成括號再壓入的。
如果前綴和小於零,那麼從取出 的堆首變成左括號,且所有的左括號不能更改了,所以將 清空。
做完一次遍歷後,可能左括號的個數比右括號多了即爲 。所以將前綴和用線段樹維護,支持區間加,查詢區間最小值。每次從 中找出堆首第 位的左括號,如果將該左括號改成右括號,會對 。所以用線段樹修改,然後查詢該區間的最小值,若 則保留修改,否則撤回修改。
Code
堆
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5, INF = 1e12;
inline int read() {
int x = 0, f = 0; char ch = 0;
while (!isdigit(ch)) f |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return f ? -x : x;
}
char s[N];
priority_queue< pair<int, int> > q;
signed main() {
scanf("%s", s + 1);
int n = strlen(s + 1), ans = 0;
for (int i = 1; i <= n; i++) {
int a, b;
if (s[i] == '(') a = 0, b = INF;
else if (s[i] == ')') a = INF, b = 0;
else s[i] = ')', a = read(), b = read();
ans += b;
q.push(make_pair(b - a, i));
if (i & 1) {
ans -= q.top().first;
s[q.top().second] = '(';
q.pop();
}
}
if (ans >= INF) puts("-1");
else {
printf("%lld\n", ans);
puts(s + 1);
}
return 0;
}