【题解】UVA10887 Concatenation of Languages🌟🔗✨

【题解】UVA10887 Concatenation of Languages 🌟🔗✨

UVA10887 Concatenation of Languages

📌 题目大意

给定两组字符串集合:

  • 集合 A 有 n 个字符串
  • 集合 B 有 m 个字符串

将 A 中的任意一个字符串与 B 中的任意一个字符串拼接形成新字符串,统计这些新字符串中 第一次出现时的数量(重复出现的不再计数)。

⚠️ 注意:字符串可能是 空字符串,输入时要小心处理。

💡 思路分析

本题关键在于 如何高效判断字符串是否第一次出现。我们用 字典树(Trie) 🌲 来解决:

1. 构建字典树 🏗️

每个节点记录:

  • count:该字符串插入次数

  • end:是否是一个完整的单词结尾

  • re:是否已经被统计过

struct node {
    int count = 0;
    bool re = false;
    bool end = false;
    node *n[26] = {nullptr};
};

2. 插入(add)➕

把每个 v1[i] + v2[j] 插入字典树,插入时路径上不存在节点则创建:

void add(node *root, string s) {
    node *p = root;
    for (char c : s) {
        int i = c - 'a';
        if (!p->n[i])
            p->n[i] = new node(); // 没有这个结点,就创建
        p = p->n[i];
    }
    p->end = true;  // 该节点是一个字符串的结尾
    p->count++;     // 出现次数 +1
}

3. 搜索(search)🔍

如果是第一次出现(end == true && count >= 1 && re == false),计数 +1 并标记 re = true。这样能保证重复的字符串只计一次 ✅

// 在 Trie 树中搜索一个字符串
int search(node *root, string s) {
    node *p = root;
    for (char c : s) {
        int i = c - 'a';
        if (!p->n[i]) {
            // 如果路径中断,说明字符串不存在
            return 0;
        }
        p = p->n[i];
    }
    // 如果是一个完整字符串且出现次数≥1且没被统计过
    if (p->end && p->count >= 1 && !(p->re)) {
        p->re = true; // 标记为已统计
        return 1;     // 表示找到一个有效的字符串
    }
    return 0;
}

4. 空字符串处理 ⚠️

因为可能出现空字符串 "",不能直接 cin >>(它会跳过空行),必须用 getline() 读取。

📂 核心代码

#include<bits/stdc++.h>
using namespace std;

struct node {
    int count = 0;
    bool re = false;
    bool end = false;
    node *n[26] = {nullptr};
};

void add(node *root, string s) {
    node *p = root;
    for (char c : s) {
        int i = c - 'a';
        if (!p->n[i])
            p->n[i] = new node();
        p = p->n[i];
    }
    p->end = true;
    p->count++;
}

int search(node *root, string s) {
    node *p = root;
    for (char c : s) {
        int i = c - 'a';
        if (!p->n[i]) {
            return 0;
        }
        p = p->n[i];
    }
    if (p->end && p->count >= 1 && !(p->re)) {
        p->re = true;
        return 1;
    }
    return 0;
}

node* createT() {
    return new node();
}

int main() {
    int t;
    cin >> t;
    int k = 1;
    
    while(t--) {
        node *root = createT();
        int n, m;
        cin >> n >> m;
        getchar(); // 重要:消耗换行符
        
        vector<string> v1(n), v2(m), v;
        
        for(int i = 0; i < n; i++) {
            getline(cin, v1[i]);
        }
        
        for(int i = 0; i < m; i++) {
            getline(cin, v2[i]);
        }
        
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                string s = v1[i] + v2[j];
                v.push_back(s);
                add(root, s);
            }
        }
        
        int ans = 0;
        for(string s : v) {
            if(search(root, s) == 1) {
                ans++;
            }
        }
        
        cout << "Case " << k << ": " << ans << endl;
        k++;
    }
    return 0;
}

⏱️ 复杂度分析

  • 构建 TrieO(L × n × m)(L 为字符串平均长度)

  • 查询O(L × n × m)

  • 空间O(L × n × m)(字典树节点数)

虽然看起来复杂度较高,但因为字母表只有 26 个,且 Trie 节点共享前缀,所以实际性能很可观 ⚡

🔑 关键技巧

1. 输入处理陷阱 ⚠️

cin >> n >> m;
getchar(); // 必须!消耗换行符

2. 去重计数的精髓 🧠

使用 re 标记字段确保每个唯一字符串只被计算一次:

  • 第一次遇到:re = false → 返回1,设置 re = true

  • 后续遇到:re = true → 返回0

3. 空字符串处理 📝

题目虽然没有明确提到空字符串,但测试数据中会包含,必须用 getline() 处理。

posted @ 2025-08-14 15:19  开珥  阅读(6)  评论(0)    收藏  举报