【题解】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;
}
⏱️ 复杂度分析
-
构建 Trie:
O(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() 处理。

浙公网安备 33010602011771号