魔法练习
题目描述
在第四届圣杯之战中,时臣其实是预见到麻婆和金闪闪会背叛他的。所以在正式开战前,时臣决定传授祖传宝石魔法给凛,就给了凛魔法图鉴。
在魔法图鉴中,每条魔法都有一个密钥,是一个由“(”、“)”和“?”组成的魔法符文。时臣同时教凛密钥的破解方法:对于第i个为“?”的符文,可以输入魔力使其变为“(”,或输入的魔力使其变为“)”。只要最终魔法符文都变为合法,匹配的括号符文,就可以瞬间学会该魔法。
凛当然想学会全部魔法,但她还是个小萝莉,魔力有限,她希望,能用最小的魔力,学习全部的魔法(使符文变成合法的括号匹配序列)。
对于40%的数据,n ≤ 100
对于60%的数据,n ≤ 1000
对于100%的数据,n ≤ 100000,n为偶数(n为字符串的长度)
输入格式 2088.in
这题目包含多组测试数据(不超过10组)。
每组测试数据,第一行为一个没有空格,偶数长度的字符串,保证由“(”、“)”和“?”组成,长度不超过100000。然后接下来的m行(m为字符串中“?”的个数),每行有两个整数Ai和Bi,(1≤Ai,Bi ≤1000000),分别代表将第i个符文转换为“(”和“)”所需的魔力。
输出格式 2088.out
若有解,输出所需最小魔力;若无解,输出-1。
输入样例 2088.in
(??)
1 2
2 8
输出样例 2088.out
4
考试时,看到这题的第一反应是DP,不过当时没有细想。实际上,DP可以拿60分,也是不错的分数了。
DP做法:
定义f[i][j]为前i个字符,其中左括号与右括号数量的差值为j所需的最小魔力。
那么,第i个字符可能是左括号,也有可能是右括号,因此,方程为:
f[i][j]=min(f[i-1][j-1]+a[i],f[i-1][j+1]+b[i])。
最后f[size][0]即为所求。记得考虑无解的情况。
代码如下:(ywq)
#include <algorithm>
#include <cstring>
#include <cstdio>
using std::min;
const long long INF = 0xFFFFFFFFFFFFFF;
const int MAXN = 100010;
char st[MAXN];
int len, a[MAXN], b[MAXN], ln[MAXN], uns;
long long f[MAXN], g[MAXN];
int main() {
while (scanf("%s", st+1) != EOF) {
uns = 0;
int len = strlen(st+1);
for (int i=1; i<=len; i++)
if (st[i] == '?') {
ln[i] = ++uns;
scanf("%d %d", &a[uns], &b[uns]);
}
for (int i=0; i<=len; i++)
f[i] = g[i] = INF;
f[0] = 0;
for (int i=1; i<=len; i++) {
if (st[i] == '(')
for (int j=1; j<=len; j++)
g[j] = f[j-1];
else if (st[i] == ')')
for (int j=0; j<=len; j++)
g[j] = f[j+1];
else if (st[i] == '?') {
g[0] = f[1] + b[ln[i]];//一样多
g[len] = f[len-1] + a[ln[i]];//全为左括号
for (int j=1; j<len; j++)
g[j] = min(f[j+1] + b[ln[i]], f[j-1] + a[ln[i]]);
}
for (int i=0; i<=len; i++) {
f[i] = g[i];
g[i] = INF;
}
}
if (f[0] >= INF) puts("-1");
else printf("%lld\n", f[0]);
}
return 0;
}
正解:贪心。
明确一个必要的条件:任意时刻,左括号的数量必须大于等于右括号的数量。因此,我们可以这样做:
一开始,先假设所有的“?”都变成了右括号,这些花费计入ans中(当然,这很可能是不合法的)。现在从头扫起,将其修改成合法序列。记录一个cha,为左括号数量与右括号数量的差值。如果某一时刻,cha小于0,则需要将它前面的一个或者自己修改成左括号,并且花费要尽可能小,计入ans中。此时,这个被修改的括号就失去了价值,那就将它弹出可选的序列。(此查询最小值/弹出可选序列的操作可以使用线段树或优先队列或堆)
最后答案,即为ans。
不过别忘了,需要考虑无解的情况。如果需要修改时,可选序列为空,则无解。如果最后左右括号数量不相等,也是无解。
时间复杂度:O(NlogN)(N为字符串长度)
正确性证明?暂略。(以后补上,太玄学了,完全没想到)
代码如下:
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<stack>
#include<queue>
using namespace std;
const int MAXN=1e5+5;
priority_queue<long long,vector<long long>,greater<long long> >q;
int n,size,le,ri,k,ok;
long long a[MAXN],b[MAXN],tree[4*MAXN],c[MAXN];
long long ans;
string s;
void init()
{
k=0;
size=s.size();
ans=0;
for(int i=0;i<size;i++)
{
if(s[i]=='?')
{
scanf("%lld%lld",&a[i],&b[i]);
c[++k]=a[i]-b[i];
ans+=b[i];
}
}
}
void solve()
{
ok=0;
int cha=0;
while(!q.empty()) q.pop();
for(int i=0;i<size;i++)
{
if(s[i]=='?') q.push(c[++ok]);
if(s[i]=='(') cha++;
else cha--;
if(cha<0)
{
if(q.empty())
{
ans=-1;
return;
}
else
{
ans+=q.top();
q.pop();
cha+=2;
}
}
}
if(cha>0) ans=-1;
}
int main()
{
while(cin>>s)
{
init();
solve();
cout<<ans<<endl;
}
return 0;
}