40%:
暴力枚举每一个 ? 是变成左括号还是右括号,再检查合法性,取费用最小的合法方案。
时间复杂度: O(2m) ,其实我觉得对于 n≤100 ,很容易构造出 m 稍微大一点的数据卡掉这种暴力,但出题人居然没有卡。
(反思:但我在考试的时候居然打错了暴力,多组数据忘记清空,只得了 20 分)
60%:
如果做出了 1989“圆括号”一题,再结合上面的暴力,不难想到 DP 的方法。
记
注意转移的过程中要保证任意时刻左括号个数都必须多于或等于右括号个数。
对于已给定的括号,可以直接跳过( f[i][j]←f[i−1][j] ),也可以通过令 Ai=0,Bi=∞ (当前固定为左括号)或 Ai=∞,Bi=0 (当前固定为左括号)的方法,使得给定的括号一定被选择到。但是要注意有一个隐患,小心出现无穷大不够大的情况。
时间复杂度: O(n2)
100%:
要保证满足条件,决策位置和合法性都不能不考虑,上面的 DP 已是最优可能,但无法解决 105 这么大规模。因此正解并不是对上面的 DP 再进行玄学优化。
考试的时候其实是有想到贪心的,但却无从下手。正解就是贪心。
输入的时候,先算出每个 ? 的 Ai−Bi ,记为 diffi 。
第一步,什么都不考虑,把全部都变成右括号。当前费用为 ∑Bi 。
之后,从第 1 位开始,从左向右逐位考虑,将到目前为止左括号个数与右括号个数的差值记为 s 。
对于一个合法的括号序列,前
所谓调整,就是在
[1,i]
中选出一个右括号把它变成左括号,选谁好呢?不妨设选了第
j
位,那么新的费用为当前费用
为了最小化费用,显然要选一个使 diffj 尽可能小的 j 。这一步,就可以用数据结构去实现:线段树,堆,优先队列都可以。
另外,类似于上面的 DP,对于已给定的括号,如果直接跳过,要注意判断可能出现的在前面选一个变成左括号的时候没得选的局面。如果是对
时间复杂度:
O(nlog2n)
。
正确性证明:暂略。
参考代码:
#include <algorithm>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int MAXN = 1e5 + 100;
int c;
long long diff[MAXN];
int l;
char str[MAXN];
long long ans;
priority_queue <long long, vector<long long>, greater<long long> > pq;
void solve() {
int k = 0, s = 0;
while (!pq.empty()) pq.pop();
for (int i = 0; i < l; i++) {
if (str[i] == '(') ++s; else --s;
if (str[i] == '?') pq.push(diff[k++]); //将来可以变成左括号
if (s == -1) //不合法的情况,在前面选一个变成左括号
if (pq.empty()) { //没得变的情况,不合法
ans = -1;
return;
} else {
ans += pq.top(); //否则选一个花费最小的变
pq.pop(); s = 1;
}
}
if (s) ans = -1; //到最后左括号比右括号多也不合法
}
int main(void) {
freopen("2088.in", "r", stdin);
freopen("2088.out", "w", stdout);
while (~scanf("%s", str)) {
c = 0; //一开始多组数据忘记清零,暴力都 WA 了
l = strlen(str);
for (int i = 0; i < l; i++) c += str[i] == '?';
ans = 0LL;
for (int i = 0; i < c; i++) {
long long A, B; scanf("%lld%lld", &A, &B);
diff[i] = A - B;
ans += B; //先全部变成右括号
}
solve();
printf("%lld\n", ans);
}
return 0;
}