1 条题解
-
0
自动搬运
来自洛谷,原作者为

Lovely_Elaina
嘟嘟嘟搬运于
2025-08-24 22:02:36,当前版本为作者最后更新于2023-01-31 21:11:18,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
前言
第一眼看到题目,
活字印刷术?其实就是给你一个
很恶心的打印机,共有三种操作:- 在当前词的尾部添加一个字母;
- 在当前次的尾部减去一个字母(至少有一个字母时);
- 打印当前词。
其次,有三个注意点:
- 添加一个字母,用这个小写字母的自身来表示;
- 删去一个字母,用减号表示;
- 打印单词时,用
P表示。
特别的:
- 所有单词都不相同;
- 打印结束时,允许有部分字母留在打印机内;
- 允许按照任意的次序打印单词。
说句题外话:这打印机谁造的。
下面的看懂了题目再来看。
思路
我们使用字典树来完成。
首先,有一个点大大的降低了题目难度,就是
打印结束时,允许有部分字母留在打印机内。众所周知,字典树很好地利用了字符串的公共前缀,这也就是上一行出现的原因。
如果我们想要操作数尽可能少,那我们的删除数的操作就要尽可能的少。
我们来模拟一下(敲黑板)。
注:为了方便观察,我改了自己造的样例的顺序。
输入: 8 abcd abcdef dfg dfgr dgrt plwoe plwpo plwpoq构建出来的树数这样的:

粉色,够温馨吧。虽然图画的有点丑,将就看吧。
让我们看回到上面那句话:
如果我们想要操作数尽可能少,那我们的删除数的操作就要尽可能的少。
怎么样,看出字典树的特征了吧。
先讲怎么遍历。
我们以中间那一坨为例:
- 先遍历
d,然后开始搜索所有子树; - 发现了
f,继续往下搜索; - 遇到了
g,发现是一个单词结尾,输出,还有子树,继续往下; - 遇到了
r,发现是一个单词结尾,输出,发现没了,停止。
啧,是回溯。
我们要删到可以继续搜,也就是说我们的 dfs 大概是这样的:
for(int i = 0; i < 26; i++){ if(存在该子节点 && 还有一个,下面会说){ 要输出的字符串 += 该子节点; dfs(该子节点); 要输出的字符串 += '-'; } }好耶。
但是这个时候有人要问了,不应该先遍历更短的子树吗?这不是删除数量更少吗?
对,这就是另外一个重点。
在输入时,我们存下最长的字符串,然后给该字符串在树中打标记。
以这个为例,我们可以发现如果想要删得更少,刚刚好
plwpoe这一条不用删。其余的所有字母都是必删的。
而
plwpoe这一条是输入中最长的。嘿嘿,懂了吗。
我们在输入的时候记下最长的,事后标记一下,优先遍历没有标记的,这样删的次数最少。
标记的函数也很简单。
void mark(string s){ int p = 0; // 当前遍历的子树的根 for(int i = 0; s[i]; i++){ //遍历,字符串 s 必存在 int x = s[i] - 'a'; tick[p][x] = 1; // 标记 p = tree[p][x]; // 下一个 } }
代码
插入:
inline void insert(string s) { int p = 0; for (int i = 0; s[i]; i++) { int x = s[i] - 'a'; if (!tree[p][x]) tree[p][x] = ++ind; // 建点 le[tree[p][x]] = x + 'a'; // 记录 p = tree[p][x]; } en[p] = 1; //标记结尾 }标记最长:
inline void mark(string s) { int p = 0; for (int i = 0; s[i]; i++) { int x = s[i] - 'a'; k[tree[p][x]] = 1; p = tree[p][x]; } }dfs:
inline void dfs(int x) { if (en[x] == 1 && x != 0) { ans++; output += "P"; } if (ans == n) { cout << output.size() << endl; for (int i = 0; output[i]; i++) cout << output[i] << endl; return; } int reg; for (int i = 0; i < 26; i++) { reg = tree[x][i]; if (k[reg] == 0 && reg != 0) { output += le[reg]; dfs(reg); output += "-"; } } for (int i = 0; i < 26; i++) { reg = tree[x][i]; if (k[reg] && reg) { output += le[reg]; dfs(reg); output += "-"; } } }其实我格式化了代码,但是原码风挺正常的。
完整代码:
#include <bits/stdc++.h> using namespace std; const int N = 1e6 + 10; bool en[N]; // 结尾 bool k[N]; // 标记 char le[N]; // 记录树的几号节点为什么 int tree[N][26]; int n, m, ind, ans; string s, output, l; inline void insert(string s) { int p = 0; for (int i = 0; s[i]; i++) { int x = s[i] - 'a'; if (!tree[p][x]) tree[p][x] = ++ind; // 建点 le[tree[p][x]] = x + 'a'; // 记录 p = tree[p][x]; } en[p] = 1; //标记结尾 } inline void mark(string s) { int p = 0; for (int i = 0; s[i]; i++) { int x = s[i] - 'a'; k[tree[p][x]] = 1; p = tree[p][x]; } } inline void dfs(int x) { if (en[x] == 1 && x != 0) { ans++; output += "P"; } if (ans == n) { cout << output.size() << endl; for (int i = 0; output[i]; i++) cout << output[i] << endl; return; } int reg; for (int i = 0; i < 26; i++) { reg = tree[x][i]; if (k[reg] == 0 && reg != 0) { output += le[reg]; dfs(reg); output += "-"; } } for (int i = 0; i < 26; i++) { reg = tree[x][i]; if (k[reg] && reg) { output += le[reg]; dfs(reg); output += "-"; } } } int main() { cin >> n; getchar(); for (int i = 1; i <= n; i++) { cin >> s; insert(s); if (l.size() < s.size()) l = s; } mark(l); dfs(0); return 0; }说两句题外话:
- 我一开始一边插入一边标记;
- 大写打成了小写。
没人像我这样吧(小声)。
- 1
信息
- ID
- 3668
- 时间
- 1000ms
- 内存
- 63MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者