题目链接

命令行解析与出现次数统计

题目描述

给定一个命令行格式的模板字符串,该模板由关键字、抉择结构 { A | B | ... } 和可选结构 [ C ] 构成。任务是:

  1. 找出所有在顶层(不被任何括号包裹)定义的“固定关键字”。
  2. 计算每个固定关键字在任何合法命令中都保证会出现的最小次数。

保证出现次数的计算规则是递归的:

  • 一个关键字的保证次数,首先是它作为固定关键字出现的次数。
  • 如果一个抉择结构 { A | B | ... } 的每一个选项中,该关键字都保证会出现(即递归计算次数 ),那么这个抉择结构会为该关键字的保证次数贡献 +1
  • 可选结构 [ C ] 内部的任何内容都不被视为保证出现。

解题思路

这是一个典型的递归结构字符串解析问题。我们可以设计一个递归函数,通过“分治”的思想来解决。该函数负责解析一个给定的模板(或子模板)字符串,并计算其中各个关键字的保证出现次数。

核心算法:递归解析与记忆化搜索

  1. 定义递归函数: 设计一个函数,例如 solve(substring),它的功能是接收一个模板字符串 substring,返回一个哈希表(map),记录该子串中每个关键字保证出现的次数。

  2. 顶层元素切分: 在 solve 函数内部,首先需要将 substring 切分成顶层的、互不相关的几个部分。例如,对于 "a { b | c } [d]",顶层部分就是 "a""{ b | c }""[d]"。这需要一个辅助函数,通过扫描字符串并维护一个嵌套层级计数器来实现,只在层级为0时进行切分。

  3. 递归计算: 遍历切分出的顶层部分:

    • 如果是关键字:直接在结果哈希表中为该关键字的计数值 +1
    • 如果是抉择结构 {...}: a. 提取括号内的内容,并根据 | 符号(同样只在顶层)将其切分成多个选项。 b. 对每一个选项字符串,递归调用 solve 函数,得到每个选项的保证次数哈希表。 c. 现在,要找出哪些关键字在所有选项中都“保证出现”。遍历第一个选项结果中的所有关键字,检查它是否存在于所有其他选项的结果中(即保证次数 )。如果某个关键字满足此条件,就在当前 solve 函数的结果哈希表中为其计数值 +1
    • 如果是可选结构 [...]:直接忽略,因为它对保证出现次数没有贡献。
  4. 记忆化: 为了避免对相同的子模板重复计算,使用一个哈希表作为缓存(记忆化)。在 solve 函数的入口处,检查当前子串是否已经计算过,如果计算过,则直接返回缓存的结果。这能将时间复杂度从指数级显著降低到多项式级。

  5. 主流程: a. 找出固定关键字:首先独立地遍历一遍输入字符串,通过记录嵌套层级,找出所有层级为0的关键字,按顺序存储起来。 b. 计算总次数:调用 solve(完整输入字符串) 得到全局的保证次数哈希表。 c. 输出结果:按顺序输出固定关键字,并从全局哈希表中查找它们对应的保证次数进行输出。

通过这种方式,我们可以清晰地处理复杂的嵌套结构,并高效地得出最终结果。

代码

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <sstream>

using namespace std;

map<string, map<string, int>> memo;

// 查找匹配的括号
int find_closing_bracket(const string& s, int start, char open, char close) {
    int level = 0;
    for (int i = start; i < s.length(); ++i) {
        if (s[i] == open) level++;
        if (s[i] == close) level--;
        if (level == 0) return i;
    }
    return -1;
}

// 递归解析函数
map<string, int> solve(string s) {
    if (memo.count(s)) {
        return memo[s];
    }

    map<string, int> counts;
    int i = 0;
    while (i < s.length()) {
        if (isspace(s[i])) {
            i++;
            continue;
        }

        if (isalpha(s[i])) {
            string keyword;
            while (i < s.length() && isalpha(s[i])) {
                keyword += s[i];
                i++;
            }
            counts[keyword]++;
        } else if (s[i] == '{') {
            int end = find_closing_bracket(s, i, '{', '}');
            string content = s.substr(i + 1, end - i - 1);
            
            vector<string> options;
            int start = 0;
            int level = 0;
            for(int j = 0; j < content.length(); ++j) {
                if(content[j] == '{' || content[j] == '[') level++;
                else if(content[j] == '}' || content[j] == ']') level--;
                else if(content[j] == '|' && level == 0) {
                    options.push_back(content.substr(start, j - start));
                    start = j + 1;
                }
            }
            options.push_back(content.substr(start));

            if (!options.empty()) {
                vector<map<string, int>> option_counts;
                for (const auto& opt : options) {
                    option_counts.push_back(solve(opt));
                }

                for (auto const& [key, val] : option_counts[0]) {
                    if (val > 0) {
                        bool in_all = true;
                        for (size_t k = 1; k < option_counts.size(); ++k) {
                            if (option_counts[k].find(key) == option_counts[k].end() || option_counts[k][key] == 0) {
                                in_all = false;
                                break;
                            }
                        }
                        if (in_all) {
                            counts[key]++;
                        }
                    }
                }
            }
            i = end + 1;
        } else if (s[i] == '[') {
            i = find_closing_bracket(s, i, '[', ']') + 1;
        } else {
            i++;
        }
    }
    return memo[s] = counts;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);

    string line;
    getline(cin, line);

    vector<string> fixed_keywords;
    int level = 0;
    for (int i = 0; i < line.length(); ) {
        if (isspace(line[i])) {
            i++;
            continue;
        }
        if (line[i] == '{' || line[i] == '[') {
            level++; i++;
        } else if (line[i] == '}' || line[i] == ']') {
            level--; i++;
        } else if (isalpha(line[i]) && level == 0) {
            string keyword;
            while(i < line.length() && isalpha(line[i])) {
                keyword += line[i];
                i++;
            }
            fixed_keywords.push_back(keyword);
        } else {
            i++;
        }
    }

    map<string, int> total_counts = solve(line);

    for (size_t i = 0; i < fixed_keywords.size(); ++i) {
        cout << fixed_keywords[i] << (i == fixed_keywords.size() - 1 ? "" : " ");
    }
    cout << "\n";

    for (size_t i = 0; i < fixed_keywords.size(); ++i) {
        cout << (total_counts.count(fixed_keywords[i]) ? total_counts[fixed_keywords[i]] : 0) << (i == fixed_keywords.size() - 1 ? "" : " ");
    }
    cout << "\n";

    return 0;
}
import java.util.*;

public class Main {
    // 记忆化缓存,存储已计算过的子串及其结果
    private static Map<String, Map<String, Integer>> memo = new HashMap<>();

    // 查找匹配的括号
    private static int findClosingBracket(String s, int start, char open, char close) {
        int level = 0;
        for (int i = start; i < s.length(); i++) {
            if (s.charAt(i) == open) level++;
            if (s.charAt(i) == close) level--;
            if (level == 0) return i;
        }
        return -1;
    }

    // 递归解析函数
    private static Map<String, Integer> solve(String s) {
        s = s.trim();
        if (memo.containsKey(s)) {
            return memo.get(s);
        }

        Map<String, Integer> counts = new HashMap<>();
        int i = 0;
        while (i < s.length()) {
            char ch = s.charAt(i);
            if (Character.isWhitespace(ch)) {
                i++;
                continue;
            }

            if (Character.isLetter(ch)) {
                StringBuilder keyword = new StringBuilder();
                while (i < s.length() && Character.isLetter(s.charAt(i))) {
                    keyword.append(s.charAt(i));
                    i++;
                }
                String kw = keyword.toString();
                counts.put(kw, counts.getOrDefault(kw, 0) + 1);
            } else if (ch == '{') {
                int end = findClosingBracket(s, i, '{', '}');
                String content = s.substring(i + 1, end).trim();
                
                List<String> options = new ArrayList<>();
                int start = 0;
                int level = 0;
                for (int j = 0; j < content.length(); j++) {
                    char c = content.charAt(j);
                    if (c == '{' || c == '[') level++;
                    else if (c == '}' || c == ']') level--;
                    else if (c == '|' && level == 0) {
                        options.add(content.substring(start, j).trim());
                        start = j + 1;
                    }
                }
                options.add(content.substring(start).trim());

                if (!options.isEmpty()) {
                    List<Map<String, Integer>> optionCounts = new ArrayList<>();
                    for (String opt : options) {
                        optionCounts.add(solve(opt));
                    }

                    for (String key : optionCounts.get(0).keySet()) {
                        if (optionCounts.get(0).get(key) > 0) {
                            boolean inAll = true;
                            for (int k = 1; k < optionCounts.size(); k++) {
                                if (optionCounts.get(k).getOrDefault(key, 0) == 0) {
                                    inAll = false;
                                    break;
                                }
                            }
                            if (inAll) {
                                counts.put(key, counts.getOrDefault(key, 0) + 1);
                            }
                        }
                    }
                }
                i = end + 1;
            } else if (ch == '[') {
                i = findClosingBracket(s, i, '[', ']') + 1;
            } else {
                i++;
            }
        }
        memo.put(s, counts);
        return counts;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String line = sc.nextLine();

        List<String> fixedKeywords = new ArrayList<>();
        int level = 0;
        for (int i = 0; i < line.length(); ) {
            char ch = line.charAt(i);
            if (Character.isWhitespace(ch)) {
                i++; continue;
            }
            if (ch == '{' || ch == '[') {
                level++; i++;
            } else if (ch == '}' || ch == ']') {
                level--; i++;
            } else if (Character.isLetter(ch) && level == 0) {
                StringBuilder keyword = new StringBuilder();
                while (i < line.length() && Character.isLetter(line.charAt(i))) {
                    keyword.append(line.charAt(i));
                    i++;
                }
                String kw = keyword.toString();
                fixedKeywords.add(kw);
            } else {
                i++;
            }
        }

        Map<String, Integer> totalCounts = solve(line);
        
        System.out.println(String.join(" ", fixedKeywords));
        
        List<String> resultCounts = new ArrayList<>();
        for (String kw : fixedKeywords) {
            resultCounts.add(String.valueOf(totalCounts.getOrDefault(kw, 0)));
        }
        System.out.println(String.join(" ", resultCounts));
    }
}
import sys

# 记忆化缓存
memo = {}

def solve(s: str) -> dict:
    # 递归解析函数
    s = s.strip()
    if s in memo:
        return memo[s]
    if not s:
        return {}

    counts = {}
    i = 0
    while i < len(s):
        if s[i].isspace():
            i += 1
            continue
        
        if s[i].isalpha():
            start = i
            while i < len(s) and s[i].isalpha():
                i += 1
            keyword = s[start:i]
            counts[keyword] = counts.get(keyword, 0) + 1
        elif s[i] == '{':
            start = i
            level = 1
            i += 1
            while i < len(s) and level > 0:
                if s[i] == '{': level += 1
                elif s[i] == '}': level -= 1
                i += 1
            
            content = s[start+1:i-1].strip()
            # 按顶层的 '|' 分割选项
            options = []
            opt_start = 0
            level = 0
            for j, char in enumerate(content):
                if char in '{[': level += 1
                elif char in '}]': level -= 1
                elif char == '|' and level == 0:
                    options.append(content[opt_start:j])
                    opt_start = j + 1
            options.append(content[opt_start:])

            if options:
                option_counts = [solve(opt) for opt in options]
                
                first_option_keywords = option_counts[0].keys()
                for kw in first_option_keywords:
                    if all(opt_map.get(kw, 0) > 0 for opt_map in option_counts):
                        counts[kw] = counts.get(kw, 0) + 1
        elif s[i] == '[':
            level = 1
            i += 1
            while i < len(s) and level > 0:
                if s[i] == '[': level += 1
                elif s[i] == ']': level -= 1
                i += 1
        else:
            i += 1
    
    memo[s] = counts
    return counts

def main():
    line = input()
    
    fixed_keywords = []
    level = 0
    i = 0
    while i < len(line):
        if line[i].isspace():
            i += 1
            continue
        
        if line[i] in '{[':
            level += 1
            i += 1
        elif line[i] in '}]':
            level -= 1
            i += 1
        elif line[i].isalpha() and level == 0:
            start = i
            while i < len(line) and line[i].isalpha():
                i += 1
            keyword = line[start:i]
            fixed_keywords.append(keyword)
        else:
            i += 1
    
    total_counts = solve(line)
    
    print(' '.join(fixed_keywords))
    result_counts = [str(total_counts.get(kw, 0)) for kw in fixed_keywords]
    print(' '.join(result_counts))

if __name__ == "__main__":
    main()

算法及复杂度

  • 算法:递归下降解析 (Recursive Descent Parsing) + 记忆化搜索 (Memoization)
  • 时间复杂度:,其中 是输入模板字符串的长度。状态数(不同子串的数量)为 。在每个状态中,我们需要对子串进行一次扫描和切分,这需要 的时间。因此,总体时间复杂度约为 。由于记忆化的存在,每个子问题的解只会被计算一次。
  • 空间复杂度:,主要用于存储记忆化表的键(子串)和值(计算结果)。