PAT_甲级_2020年冬季考试 7-4 Chemical Equation

乔梓鑫

7-4 Chemical Equation (30 分)

A chemical equation is the symbolic representation of a chemical reaction in the form of symbols and formulae, wherein the reactant entities are given on the left-hand side and the product entities on the right-hand side. For example, $$CH_4+2O_2=CO_2+2H_2O$$

means that the reactants in this chemical reaction are methane and oxygen: CH4 and O​2, and the products of this reaction are carbon dioxide and water: CO2 and H2O.

Given a set of reactants and products, you are supposed to tell that in which way we can obtain these products, provided that each reactant can be used only once. For the sake of simplicity, we will consider all the entities on the right-hand side of the equation as one single product.

Input Specification:

Each input file contains one test case. For each case, the first line gives an integer N (2≤N≤20), followed by N distinct indices of reactants. The second line gives an integer M (1≤M≤10), followed by M distinct indices of products. The index of an entity is a 2-digit number.

Then a positive integer K (≤50) is given, followed by K lines of equations, in the format:

$$ reactant_1 + reactant_2 + ... + reactant_n -> product $$

where all the reactants are distinct and are in increasing order of their indices.

Note: It is guaranteed that

  • one set of reactants will not produce two or more different products, i.e. situation like 01 + 02 -> 03 and 01 + 02 -> 04 is impossible;
  • a reactant cannot be its product unless it is the only one on the left-hand side, i.e. 01 -> 01 is always true (no matter the equation is given or not), but 01 + 02 -> 01 is impossible; and
  • there are never more than 5 different ways of obtaining a product given in the equations list.

Output Specification:

For each case, print the equations that use the given reactants to obtain all the given products. Note that each reactant can be used only once.

Each equation occupies a line, in the same format as we see in the inputs. The equations must be print in the same order as the products given in the input. For each product in order, if the solution is not unique, always print the one with the smallest sequence of reactants -- A sequence $${ a_1,⋯,a_​m }$$ is said to be smaller than another sequence $${ b_1 ,⋯,b​_n } $$if there exists $$1≤i≤min(m,n)$$ so that $$a_j​​ =b_j \;\; for \;\;all\;\; j<i, and\;\; a_i​​ <b_i.$$

It is guaranteed that at least one solution exists.

Sample Input:

8 09 05 03 04 02 01 16 10
3 08 03 04
6
03 + 09 -> 08
02 + 08 -> 04
02 + 04 -> 03
01 + 05 -> 03
01 + 09 + 16 -> 03
02 + 03 + 05 -> 08

Sample Output:

02 + 03 + 05 -> 08
01 + 09 + 16 -> 03
04 -> 04

题目要求

image.png

题目大意

给定n个反应物,m个生成物和k个化学反应方程式。要求根据反应物和化学方程式输出可以获得生成物的化学方程式,生成规则如下:

  • 1、一组反应物只会生成一种product
  • 2、除了 pro->pro 的情况,一个化学方程式是不可能出现反应物中存在生成物的情况
  • 3、对于有多个生成的化学式输出字典序最小的那个。
  • 4、每一种反应物只能使用一次

算法思路

此题好像只能使用深度优先遍历来处理,原因在于需要将所有的生成物进行生成,并且题目保证一定存在一个解。那么使用贪心算法取每一次获得当前生成物字典序最小的有可能会导致后面的生成物无法生成的情况。为了方便进行搜索我们使用结构体Equation来封装我们需要的数据,这里将算法的过程分为预处理和DFS。

  • 数据结构:
struct Equation {
    string equation;
    vector<int> reacts;
    int prod{};

    friend bool operator<(const Equation &a, const Equation &b) {
        return a.equation < b.equation;
    }
};
unordered_map<int, bool> reactants;// 标记当前反应物是否可以使用
unordered_map<int, set<Equation>> products;// 根据product建立到所有生成该生成物的Equation集合

vector<int> pro;// 所有需要生成的生成物编号
vector<string> ans;// 所有的结果集合

数据的预处理

首先在输入所有反应物的时候标记reactants[index] = true,在输入生成物的时候需要建立product->product这样的方程式添加到products集合中,并同时标记当前成物为反应物。最后在输入每一行的化学方程式的时候使用process函数进行处理,将其封装为一个Equation,并添加到products集合中。

DFS

我们使用输入的生成物数组pro的下标来代表当前搜索的位置,起始为0,终止位置为m-1,DFS(proCur,proEnd)的含义为数组[proCur,proEnd-1]的生成物是否可以被生成,如果可以返回true,否则返回false。那么原问题的解就是DFS(0,m)。那么递归边界和递归体分别为:

  • 递归边界:当我们当前遍历的下标为m的时候说明遍历完毕,获得了一组解,直接返回true。
  • 递归体:首先遍历当前生成物pro[proCur]的所有化学方程式,判断当前的方程是是否可以使用,如果可以,先标记当前化学方程式的生成物全部已经使用,并将方程式添加到ans数组中,然后进行下一层递归,判断proCur + 1以及后续的生成物是否可以被生成,如果为true,说明从proCur位置到数组末尾的所有生成物都可以被生成,返回true。否则说明后续生成物生成失败,需要回溯,先将所有的反应物标记为未使用,然后ans弹出之前添加的方程式。

注意点

  • 1、这里使用DFS主要是因为需要生成所有的生成物,一个都不能落下,所以贪心算法不可用,测试点1和5考察该点。
  • 2、每一个查询的生成物都可以生成 product->product的化学方程式,需要人为添加该式子到集合中。

提交结果

image.png

AC代码

#include<cstdio>
#include<vector>
#include<iostream>
#include<unordered_map>
#include <set>
#include <string>

using namespace std;

struct Equation {
    string equation;
    vector<int> reacts;
    int prod{};

    friend bool operator<(const Equation &a, const Equation &b) {
        return a.equation < b.equation;
    }
};

unordered_map<int, bool> reactants;// 标记当前反应物是否可以使用
unordered_map<int, set<Equation>> products;// 根据product建立到所有生成该生成物的Equation集合

vector<int> pro;// 所有需要生成的生成物编号
vector<string> ans;// 所有的结果集合

// 将s封装为一个Equation
void process(const string &s) {
    int n = s.size();
    if (n == 0) {
        return;
    }
    Equation e;
    // 最后两位为生成物编号
    e.prod = stoi(s.substr(n - 2));
    // 分解字符串获取反应物
    string temp;
    for (int i = 0; i < n; ++i) {
        if (s[i] >= '0' && s[i] <= '9') {
            temp.push_back(s[i]);
        } else if (s[i] == ' ' && !temp.empty()) {
            int v = stoi(temp);
            e.reacts.push_back(v);
            temp.clear();
        } else if (s[i] == '-') {
            break;
        }
    }
    e.equation = s;
    products[e.prod].insert(e);
}

// 判断当前的生成物pro[proCur]是否可以被生成,如果不可以就得回溯
bool DFS(int proCur, int proEnd) {
    if (proCur == proEnd) {
        // 此时已经将所有的生成物生成完毕
        return true;
    }
    // 生成物编号
    int proNo = pro[proCur];
    // 遍历当前生成物proNo的所有化学方程式
    for (const auto &allEquations:products[proNo]) {
        // 判断当前的方程是是否可以使用(所有的反应物是否都没有被使用过)
        bool isvalid = true;
        for (int react:allEquations.reacts) {
            if (!reactants[react]) {
                // 当前反应物被使用过
                isvalid = false;
                break;
            }
        }
        if (!isvalid) {
            continue;
        }
        // 存在一种解
        ans.push_back(allEquations.equation);
        // 然后将所有的反应物都进行标记使用过
        for (int react:allEquations.reacts) {
            reactants[react] = false;
        }
        // 递归查询后续的生成物是否可以生成
        if (DFS(proCur + 1, proEnd)) {
            // 可以生成说明找到了一组解,直接返回即可
            return true;
        }
        // 当前选择的化学方程式使得后续的生成物无法生成,需要回溯
        for (int react:allEquations.reacts) {
            reactants[react] = true;
        }
        ans.pop_back();
    }
    // 遍历了所有的化学生成式都没有办法获得当前生成物
    return false;
}

int main() {
    // n为反应物数量,m为生成物数量
    int n, m;
    scanf("%d", &n);
    int index;
    for (int i = 0; i < n; ++i) {
        scanf("%d", &index);
        // 标记所有给出的反应式
        reactants[index] = true;
    }
    scanf("%d", &m);
    for (int i = 0; i < m; ++i) {
        string s;
        cin >> s;
        pro.push_back(stoi(s));
        // 每一个反应物都存在一个 pro->pro 的化学方程式
        Equation e;
        e.equation.append(s);
        e.equation.append(" -> ");
        e.equation.append(s);
        e.prod = pro[i];
        e.reacts.push_back(pro[i]);
        // 标记当前的生成物同时为反应物
        reactants[pro[i]] = true;
        // 将当前的pro->pro的化学方程式添加到products集合中
        products[pro[i]].insert(e);
    }
    int k;
    scanf("%d", &k);
    // 吸收回车
    getchar();
    string s;
    for (int i = 0; i < k; ++i) {
        getline(cin, s);
        // 处理每一个化学方程式,封装为Equation
        process(s);
    }
    // 深度搜索,保证获得所有的生成物
    DFS(0, m);
    // 输出结果集合
    for (const auto &v:ans) {
        cout << v << endl;
    }
    return 0;
}
阅读 85

主要分享个人学习经验和心得

543 声望
10 粉丝
0 条评论
你知道吗?

主要分享个人学习经验和心得

543 声望
10 粉丝
宣传栏