魔法练习

魔法练习

题目描述

在第四届圣杯之战中,时臣其实是预见到麻婆和金闪闪会背叛他的。所以在正式开战前,时臣决定传授祖传宝石魔法给凛,就给了凛魔法图鉴。
在魔法图鉴中,每条魔法都有一个密钥,是一个由“(”“)”“?”组成的魔法符文。时臣同时教凛密钥的破解方法:对于第i个为“?”的符文,可以输入魔力使其变为“(”,或输入的魔力使其变为“)”。只要最终魔法符文都变为合法,匹配的括号符文,就可以瞬间学会该魔法。
凛当然想学会全部魔法,但她还是个小萝莉,魔力有限,她希望,能用最小的魔力,学习全部的魔法(使符文变成合法的括号匹配序列)。


对于40%的数据,n ≤ 100
对于60%的数据,n ≤ 1000
对于100%的数据,n ≤ 100000n为偶数(n为字符串的长度)

输入格式 2088.in

这题目包含多组测试数据(不超过10组)。
每组测试数据,第一行为一个没有空格,偶数长度的字符串,保证由“(”“)”“?”组成,长度不超过100000。然后接下来的m行(m为字符串中“?”的个数),每行有两个整数AiBi(1≤AiBi ≤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;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我们来进行一道综合练习。 假设我们正在设计一个游戏,游戏中有多种角色,每种角色都有自己的属性和行为。我们来考虑如何使用Java继承和多态来实现这个游戏。 首先,我们可以定义一个抽象类`Character`,表示所有角色的基类,其中包含角色的基本属性和方法: ```java public abstract class Character { protected String name; // 角色名 protected int level; // 等级 protected int health; // 生命值 protected int mana; // 法力值 public Character(String name, int level, int health, int mana) { this.name = name; this.level = level; this.health = health; this.mana = mana; } public abstract void attack(); // 攻击方法 public abstract void defend(); // 防御方法 // getter和setter方法 // ... } ``` 然后,我们可以定义具体的角色类,例如战士`Warrior`和法师`Mage`,它们分别继承自`Character`类,并实现自己的攻击和防御方法: ```java public class Warrior extends Character { private int strength; // 力量属性 public Warrior(String name, int level, int health, int mana, int strength) { super(name, level, health, mana); this.strength = strength; } @Override public void attack() { System.out.println("战士" + name + "使用大剑攻击敌人!"); } @Override public void defend() { System.out.println("战士" + name + "使用盾牌防御敌人的攻击!"); } // getter和setter方法 // ... } public class Mage extends Character { private int intelligence; // 智力属性 public Mage(String name, int level, int health, int mana, int intelligence) { super(name, level, health, mana); this.intelligence = intelligence; } @Override public void attack() { System.out.println("法师" + name + "释放火球术攻击敌人!"); } @Override public void defend() { System.out.println("法师" + name + "使用魔法盾防御敌人的攻击!"); } // getter和setter方法 // ... } ``` 最后,我们可以在游戏中创建不同的角色对象,并进行攻击和防御操作: ```java public class Game { public static void main(String[] args) { Character warrior = new Warrior("张三", 10, 100, 50, 20); Character mage = new Mage("李四", 10, 80, 100, 30); warrior.attack(); mage.defend(); } } ``` 以上就是一个简单的继承和多态的综合练习,通过这个例子,我们可以发现继承和多态能够很好地实现代码的复用和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值