CF3D Least Cost Bracket Sequence(貪心 + 堆 + 線段樹)

Description

給定一個由 ()? 構成的序列,其中 ? 可以換成左括號和右括號。每一個 ? 替換成兩個括號都有對應的代價,求讓序列爲括號匹配序列的替換最小代價和方案。

Solution

有許多做法,暴力就是 dp。大概是 fi,jf_{i,j} 爲前 ii 個字符,左括號與右括號之差爲 jj 的最小代價。顯然轉移是 O(n2)O(n^2)。還是那句話——dp 不行考慮貪心。

可以將所有的括號和問號都當成 ? 來處理,那麼 ( 變成右括號的代價爲正無窮,) 變成左括號的代價也是正無窮,自己變自己沒有花費。默認所有的 ? 都是右括號,需要的時候取出最優的 ? 變成左括號,可以維護變成兩個括號的花費之差用堆維護。

如果考場上想不到那麼清新的做法呢。按自己想法來,把左括號看作 +1+1,右括號爲 1-1,做一遍前綴和。如果有 sumi0sumn=0sum_i \ge 0 \land sum_n = 0,那麼原序列爲合法括號匹配序列。

遍歷一次原序列,把 ? 變成花費小的,同時做前綴和。維護兩個堆 left\text{left}right\text{right}left\text{left} 的堆首爲堆中的右括號中可以變成左括號多出的花費差最小的,right\text{right} 同理,並且 left\text{left}right\text{right} 全是 ? 變成括號再壓入的。

如果前綴和小於零,那麼從取出 left\text{left} 的堆首變成左括號,且所有的左括號不能更改了,所以將 right\text{right} 清空。

做完一次遍歷後,可能左括號的個數比右括號多了即爲 sumn>0sum_n > 0。所以將前綴和用線段樹維護,支持區間加,查詢區間最小值。每次從 right\text{right} 中找出堆首第 pp 位的左括號,如果將該左括號改成右括號,會對 sump+1n2sum_{p + 1 \sim n} - 2。所以用線段樹修改,然後查詢該區間的最小值,若 0\ge 0 則保留修改,否則撤回修改。

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章