Trie树ヾ(=゚・゚=)ノ喵♪
一、想说的话
感觉难度不是很大,两个操作,存和查。遍历操作会和图相结合,难度不大的时候用深搜比较多
二、[TJOI2010]阅读理解
题目描述
英语老师留了 N 篇阅读理解作业,但是每篇英文短文都有很多生词需要查字典,为了节约时间,现在要做个统计,算一算某些生词都在哪几篇短文中出现过。
输入格式
第一行为整数 N ,表示短文篇数,其中每篇短文只含空格和小写字母。
按下来的 N 行,每行描述一篇短文。每行的开头是一个整数 L ,表示这篇短文由 L 个单词组成。接下来是 L 个单词,单词之间用一个空格分隔。
然后为一个整数 M ,表示要做几次询问。后面有 M 行,每行表示一个要统计的生词。
输出格式
对于每个生词输出一行,统计其在哪几篇短文中出现过,并按从小到大输出短文的序号,序号不应有重复,序号之间用一个空格隔开(注意第一个序号的前面和最后一个序号的后面不应有空格)。如果该单词一直没出现过,则输出一个空行。
输入输出样例
输入 #1
3
9 you are a good boy ha ha o yeah
13 o my god you like bleach naruto one piece and so do i
11 but i do not think you will get all the points
5
you
i
o
all
naruto
输出 #1
1 2 3
2 3
1 2
3
2
说明/提示
对于 30% 的数据, 1≤M≤10^3 。
对于 100% 的数据,1≤M≤104,1≤*N*≤103 。
每篇短文长度(含相邻单词之间的空格)≤5×10^3 字符,每个单词长度 ≤20 字符。
每个测试点时限 2 秒。
感谢@钟梓俊添加的一组数据。
先存,然后遍历查,感觉没什么特别的地方,主要是熟悉一下板子
AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n, m, l, tot = 0;
char s[1001];
int tri[300007][26];
bitset<1001> b[500007];
inline void add(char *s, int x){
int rt = 0;
for(int i = 0; s[i]; i++){
int v = s[i] - 'a';
if(!tri[rt][v]){
tri[rt][v] = ++tot;
}
rt = tri[rt][v];
}
b[rt][x] = 1;
}
inline void query(char *s){
int rt = 0;
for(int i = 0; s[i]; i++){
int v = s[i] - 'a';
if(!tri[rt][v]){
cout<<' '<<endl;
return;
}
rt = tri[rt][v];
}
for(int i = 1; i <= n; i++){
if(b[rt][i] == 1){
cout<<i<<' ';
}
}
cout<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin >> n;
for(int i = 1; i <= n; i++){
cin >> l;
for(int j = 1; j <= l; j++){
cin >> s;
add(s, i);
}
}
cin >> m;
for(int i = 1; i <= m; i++){
cin >> s;
query(s);
}
return 0;
}
三、[HNOI2004]L语言
题目描述
标点符号的出现晚于文字的出现,所以以前的语言都是没有标点的。现在你要处理的就是一段没有标点的文章。
一段文章 T 是由若干小写字母构成。一个单词 W 也是由若干小写字母构成。一个字典 D 是若干个单词的集合。我们称一段文章 T 在某个字典 D 下是可以被理解的,是指如果文章 T 可以被分成若干部分,且每一个部分都是字典 D 中的单词。
例如字典 D 中包括单词 is,name,what,your,则文章 whatisyourname 是在字典 D 下可以被理解的,因为它可以分成 4 个单词:what,is,your,name,且每个单词都属于字典 D,而文章 whatisyouname 在字典 D 下不能被理解,但可以在字典 D′=D∪{you} 下被理解。这段文章的一个前缀 whatis,也可以在字典 D 下被理解,而且是在字典 D 下能够被理解的最长的前缀。
给定一个字典 D,你的程序需要判断若干段文章在字典 D 下是否能够被理解。并给出其在字典 D 下能够被理解的最长前缀的位置。
输入格式
第一行两个整数 n 和 m,表示字典 D 中有 n 个单词,且有 m 段文章需要被处理。
接下来 n 行,每行一个字符串 s,表示字典 D 中的一个单词。
接下来 m 行,每行一个字符串 t,表示一篇文章。
输出格式
对于输入的每一篇文章,你需要输出一行一个整数,表示这段文章在字典 D 可以被理解的最长前缀的位置。
输入输出样例
输入 #1
4 3
is
name
what
your
whatisyourname
whatisyouname
whaisyourname
输出 #1
14
6
0
说明/提示
样例 1 解释
- 对于第一个询问,整段文章
whatisyourname
都能被理解。 - 对于第二个询问,前缀
whatis
能够被理解。 - 对于第三个询问,没有任何前缀能够被理解。
数据规模与约定
- 对于 80% 的数据,保证 m≤20,∣t∣≤10^6。
- 对于 100% 的数据,保证 1≤n≤20,1≤m≤50,1≤∣s∣≤10,1≤∣t∣≤2×106,s 与 t 中均只含小写英文字母。
提示
- 请注意数据读入对程序效率造成的影响。
- 请注意【数据规模与约定】中标注的串长是单串长度,并不是字符串长度和。
说明
本题数据有加强,其中前 80% 的数据为原测试数据。
这题需要对踹树有更好的理解,存的时候是一样存,然后加一个flag,来控制结尾,然后查的时候,就是正常查,如果能查到结尾(前面存了结尾的)就标记一下,然后查下一个,最后把标记存起来,碰到相同的就直接用。
AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n, m;
char s[2000005]; //输入数据
int trie[1005][28];//字典树数组
int cnt = 0;
map<string,bool>m1;//记录字符串s是否计算过,计算过就将m1[s]设为true
map<string,int>m2;//记录字符串s是否计算过,计算过就将m2[s]设为针对s的答案
bool flag[10005];
bool f[2000010];
inline void add(){
scanf("%s", s + 1);
int len = strlen(s + 1);
int pos = 0;
for(int i = 1; i <= len; i++){
int c = s[i] - 'a';
if(!trie[pos][c]){
trie[pos][c] = ++cnt;
}
pos = trie[pos][c];
}
flag[pos] = 1;
}
inline int query(){
scanf("%s", s + 1);
if(m1[s+1]){
return m2[s+1];
}
int l = strlen(s + 1);
int pos = 0;
int ans;
memset(f, false, sizeof(f));
//标记断点
f[0] = true;
//往前走
for(int i = 0; i <= l; i++){
//如果是一个单词的结尾字母就继续,否则继续往后扫描
if(!f[i]){
continue;
}
else{
//记录目前能识别的最后一个位置
ans = i;
}
for(int j = i + 1, p = 0; j <= l; j++){
int c = s[j] - 'a';
p = trie[p][c];
if(!p){
break;
}
if(flag[p]){
f[j] = true;
}
}
}
m1[s + 1] = true;
m2[s + 1] = ans;
return ans;
}
int main()
{
ios::sync_with_stdio(false);
n = r, m = r;
while(n--){
add();
}
while(m--){
cout<<query()<<endl;
}
return 0;
}
四、[USACO08DEC]Secret Message G
题目描述
Bessie is leading the cows in an attempt to escape! To do this, the cows are sending secret binary messages to each other.
Ever the clever counterspy, Farmer John has intercepted the first bi (1≤bi≤10,000) bits of each of MM (1≤M≤50,000) of these secret binary messages.
He has compiled a list of N (1≤N≤50,000) partial codewords that he thinks the cows are using. Sadly, he only knows the first cj (1≤cj≤10,000) bits of codeword j.
For each codeword j, he wants to know how many of the intercepted messages match that codeword (i.e., for codeword j, how many times does a message and the codeword have the same initial bits). Your job is to compute this number.
The total number of bits in the input (i.e., the sum of the bi and the cj) will not exceed 500,000.
贝茜正在领导奶牛们逃跑.为了联络,奶牛们互相发送秘密信息.
信息是二进制的,共有 M(1≤M≤50000)条,反间谍能力很强的约翰已经部分拦截了这些信息,知道了第 i 条二进制信息的前 bi(l≤bi≤10000)位,他同时知道,奶牛使用 N(1≤N≤50000)条暗号.但是,他仅仅知道第 j 条暗号的前 cj(1≤cj≤10000)位。
对于每条暗号 jj,他想知道有多少截得的信息能够和它匹配。也就是说,有多少信息和这条暗号有着相同的前缀。当然,这个前缀长度必须等于暗号和那条信息长度的较小者。
在输入文件中,位的总数(即 ∑bi+∑ci)不会超过 500000。
输入格式
Line 1: Two integers: M and N.
Lines 2 M+1: Line i+1 describes intercepted code i with an integer bi followed by bi space-separated 0
's and 1
's.
Lines M+2…M+N+1: Line M+j+1 describes codeword j with an integer cj followed by cj space-separated 0
's and 1
's.
输出格式
Lines 1…M: Line j: The number of messages that the j-th codeword could match.
输入输出样例
输入 #1
4 5
3 0 1 0
1 1
3 1 0 0
3 1 1 0
1 0
1 1
2 0 1
5 0 1 0 0 1
2 1 1
输出 #1
1
3
1
1
2
说明/提示
Four messages; five codewords.
The intercepted messages start with 010, 1, 100, and 110.
The possible codewords start with 0, 1, 01, 01001, and 11.
0 matches only 010: 1 match
1 matches 1, 100, and 110: 3 matches
01 matches only 010: 1 match
01001 matches 010: 1 match
11 matches 1 and 110: 2 matches
字典树,存的时候记录每个点的路过数和以该节点结尾数,最后遍历的时候把结尾数往上加,如果能整个都遍历完,再把后面的路过数加上即可。
AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int N = 500005;
int trie[N][3];
int sum[N], ed[N], tot = 0;
bool q[N];
int main()
{
ios::sync_with_stdio(false);
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++){
int t;
cin >> t;
int pos = 0;
for(int j = 1; j <= t; j++){
int k;
cin >> k;
if(!trie[pos][k]){
trie[pos][k] = ++tot;
}
pos = trie[pos][k];
sum[pos]++;
}
ed[pos]++;
}
// cout<<ed[4]<<endl;
for(int i = 1; i <= m; i++){
int t;
cin >> t;
int res = 0;
int pos = 0;
for(int j = 1; j <= t; j++){
cin >> q[j];
}
int j;
for(j = 1; j <= t; j++){
int k = q[j];
if(!trie[pos][k]){
cout << res << endl;
break;
}
pos = trie[pos][k];
res += ed[pos];
}
if(j == t + 1){
cout<<res - ed[pos] + sum[pos]<<endl;
}
}
return 0;
}
五、 [USACO12DEC]First! G
题目描述
Bessie has been playing with strings again. She found that by
changing the order of the alphabet she could make some strings come before all the others lexicographically (dictionary ordering).
For instance Bessie found that for the strings “omm”, “moo”, “mom”, and “ommnom” she could make “mom” appear first using the standard alphabet and that she could make “omm” appear first using the alphabet
“abcdefghijklonmpqrstuvwxyz”. However, Bessie couldn’t figure out any way to make “moo” or “ommnom” appear first.
Help Bessie by computing which strings in the input could be
lexicographically first by rearranging the order of the alphabet. To compute if string X is lexicographically before string Y find the index of the first character in which they differ, j. If no such index exists then X is lexicographically before Y if X is shorter than Y. Otherwise X is lexicographically before Y if X[j] occurs earlier in the alphabet than Y[j].
Bessie一直在研究字符串。她发现,通过改变字母表的顺序,她可以按改变后的字母表来排列字符串(字典序大小排列)。
例如,Bessie发现,对于字符串串“omm”,“moo”,“mom”和“ommnom”,她可以使用标准字母表使“mom”排在第一个(即字典序最小),她也可以使用字母表“abcdefghijklonmpqrstuvwxyz”使得“omm”排在第一个。然而,Bessie想不出任何方法(改变字母表顺序)使得“moo”或“ommnom”排在第一个。
接下来让我们通过重新排列字母表的顺序来计算输入中有哪些字符串可以排在第一个(即字典序最小),从而帮助Bessie。
要计算字符串X和字符串Y按照重新排列过的字母表顺序来排列的顺序,先找到它们第一个不同的字母X[i]与Y[i],按重排后的字母表顺序比较,若X[i]比Y[i]先,则X的字典序比Y小,即X排在Y前;若没有不同的字母,则比较X与Y长度,若X比Y短,则X的字典序比Y小,即X排在Y前。
输入格式
* Line 1: A single line containing N (1 <= N <= 30,000), the number of strings Bessie is playing with.
* Lines 2…1+N: Each line contains a non-empty string. The total number of characters in all strings will be no more than 300,000. All characters in input will be lowercase characters ‘a’ through ‘z’. Input will contain no duplicate strings.
第1行:一个数字N(1 <= N <= 30,000),Bessie正在研究的字符串的数量。
第2~N+1行:每行包含一个非空字符串。所有字符串包含的字符总数不会超过300,000。 输入中的所有字符都是小写字母,即a~z。 输入不包含重复的字符串。
输出格式
* Line 1: A single line containing K, the number of strings that could be lexicographically first.
* Lines 2…1+K: The (1+i)th line should contain the ith string that could be lexicographically first. Strings should be output in the same order they were given in the input.
第1行:一个数字K,表示按重排后的字母表顺序排列的字符串有多少可以排在第一个数量。
第2~K+1行:第i+1行包含第i个按重排后的字母表顺序排列后可以排在第一个的字符串。字符串应该按照它们在输入中的顺序来输出。
输入输出样例
输入 #1
4 omm moo mom ommnom
输出 #1复制
2
omm
mom
说明/提示
The example from the problem statement.
Only “omm” and “mom” can be ordered first.
样例即是题目描述中的例子,只有“omm”和“mom”在各自特定的字典序下可以被排列在第一个。
字典树,然后想想什么情况下不能排第一,首先如果前缀存在不能排第一,这个上一道题处理过相同的情况。然后是成环的情况,注释里面有。
AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int N = 3e5 + 5;
int n, tot = 0, trie[N][30], ans = 0;
bool ed[N];
string s[N];
bool isRig[N];
int in[26], e[26][26];
void add(string s){
int pos = 1;
int len = s.size();
for(int i = 0; i < len; i++){
int c = s[i] - 'a';
if(!trie[pos][c]){
trie[pos][c] = ++tot;
}
pos = trie[pos][c];
}
ed[pos] = true;
}
//拓扑排序
queue<int> q;
void topoSort(){
for(; !q.empty(); q.pop());
for(int i = 0; i < 26; i++){
if(!in[i]){
q.push(i);
}
}
for(; !q.empty(); ){
int pos = q.front();
q.pop();
for(int i = 0; i < 26; i++){
if(e[pos][i]){
--in[i];
if(!in[i]){
q.push(i);
}
}
}
}
}
bool fin(string s){
int pos = 1, len = s.size();
memset(e, 0, sizeof(e));
memset(in, 0, sizeof(in));
for(int i = 0; i < len; i++){
if(ed[pos]){
return false;
}
int c = s[i] - 'a';
for(int j = 0; j < 26; j++){
//如果两点字母不同,并且在字典树中存在,就进行连边
if(c != j && trie[pos][j] && !e[c][j]){
e[c][j] = 1;
++in[j];
}
}
pos = trie[pos][c];
}
//然后进行拓扑排序,这里我们构建了一个怎样的图呢?
//其实就是把这个字符串经过的同层级的字符都拉进来(不重复)
//然后我们其实可以指定同层级里面字符串所用的字符的字典序最小
//让他指向同层级的其他字符,然后继续下一层,这样处理多次之后有可能
//会出现环,出现环就意味着这个字符串无法被设为最小字典序
topoSort();
for(int i = 0; i < 26; i++){
if(in[i]){
char c = i + 'a';
return false;
}
}
return true;
}
int main()
{
ios::sync_with_stdio(false);
cin >> n;
tot = 1;
for(int i = 1; i <= n; i++){
cin >> s[i];
add(s[i]);
}
for(int i = 1; i <= n; i++){
if(fin(s[i])){
ans++;
isRig[i] = true;
}
}
cout<<ans<<endl;
for(int i = 1; i <= n; i++){
if(isRig[i]){
cout<<s[i]<<endl;
}
}
return 0;
}
六、[JSOI2009]电子字典
题目描述
人们在英文字典中查找某个单词的时候可能不知道该单词的完整拼法,而只知道该单词的一个错误的近似拼法,这时人们可能陷入困境,为了查找一个单词而浪费大量的时间。带有模糊查询功能的电子字典能够从一定程度上解决这一问题:用户只要输入一个字符串,电子字典就返回与该单词编辑距离最小的几个单词供用户选择。
字符串a与字符串b的编辑距离是指:允许对a或b串进行下列“编辑”操作,将a变为b或b变为a,最少“编辑”次数即为距离。
- 删除串中某个位置的字母;
- 添加一个字母到串中某个位置;
- 替换串中某一位置的一个字母为另一个字母;
JSOI团队正在开发一款电子字典,你需要帮助团队实现一个用于模糊查询功能的计数部件:对于一个待查询字符串,如果它是单词,则返回-1;如果它不是单词,则返回字典中有多少个单词与它的编辑距离为1。
输入格式
第一行包含两个正整数N (N ≤ 10,000)和M (M ≤ 10,000)。
接下来的N行,每行一个字符串,第i + 1行为单词Wi。单词长度在1至20之间。
再接下来M行,每行一个字符串,第i + N + 1表示一个待查字符串Qi。待查字符串长度在1至20之间。Wi和Qi均由小写字母构成,文件中不包含多余空格。所有单词互不相同,但是查询字符串可能有重复。
输出格式
输出应包括M行,第i行为一个整数Xi。Xi = -1表示Qi为字典中的单词;否则Xi表示与Qi编辑距离为1的单词的个数。
输入输出样例
输入 #1
4 3
abcd
abcde
aabc
abced
abcd
abc
abcdd
输出 #1
-1
2
3
说明/提示
abcd在单词表中出现过;abc与单词abcd、aabc的编辑距离都是1;abcdd与单词abcd、abcde、abced的编辑距离都是1。
对50%的数据范围:N<=1,000,M<=1,000
对100%的数据范围:N<=10,000, M<=10,000
存字典树,深搜遍历每种操作,注意细节
AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int N = 1e6 +5;
int n, m, tot = 0;
bool ori;
int len;
int trie[N][30];
char s[30];
bool ed[N];
int ans;
bool v[N];
void add(char* s){
int pos = 0;
int len = strlen(s);
for(int i = 0; i < len; i++){
int c = s[i] - 'a';
if(!trie[pos][c]){
trie[pos][c] = ++tot;
}
pos = trie[pos][c];
}
ed[pos] = true;
}
void dfs(int x, int lth, bool op){
if(ori){
return;
}
if(lth == len && ed[x] && !op){
ori = true;
return;
}
if(lth == len && ed[x] && op){
if(!v[x]){
v[x] = true;
ans++;
}
return;
}
if(!op){
if(lth < len){
dfs(x, lth + 1, true);
}
for(int i = 0; i < 26; i++){
if(trie[x][i]){
dfs(trie[x][i], lth, true);
if(i != s[lth] - 'a'){
dfs(trie[x][i], lth + 1, true);
}
}
}
}
if(lth == len){
return;
}
if(trie[x][s[lth] - 'a']){
dfs(trie[x][s[lth] - 'a'], lth + 1, op);
}
}
int main()
{
ios::sync_with_stdio(false);
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> s;
add(s);
}
for(int i = 1; i <= m; i++){
ans = 0;
memset(v, false, sizeof(v));
cin >> s;
ori = false;
len = strlen(s);
dfs(0, 0, false);
if(ori){
cout << "-1" << endl;
}
else{
cout << ans <<endl;
}
}
return 0;
}
七、[IOI2008] Type Printer
题目描述
你需要利用一台可移动的打印机打印出N个单词。这种可移动式打印机是一种老式打印机,它需要你将一些小的金属块(每个包含一个字母)放到打印机上以组成单词。然后将这些小金属块压在一张纸上以打印出这个词。这种打印机允许你进行下列操作:
- 在打印机当前词的末端(尾部)添加一个字母;
- 在打印机当前词的尾部删去一个字母(将打印机当前词的最后一个字母删去)。仅当打印机当前至少有一个字母时才允许进行该操作;
- 将打印机上的当前词打印出来。 初始时打印机为空,或者说它不含任何带字母的金属块。打印结束时,允许有部分字母留在打印机内。同时也允许你按照任意的次序打印单词。
由于每一个操作都需要一定时间,所以需要你尽可能减少所需操作的总数目(将操作的总数最小化)。
你需要编写一个程序,给定所要打印的N个单词,找出以任意次序打印所有单词所需操作的最小数目,并输出一种这样的操作序列。
输入格式
- 第1行包含一个整数N, 表示你需要打印的单词数。
- 随后的N行中,每一行都包含一个单词。每个词仅由小写字母(‘a’ – ‘z’)组成,而且单词的长度为1到20个字母(包含1和20在内)。所有单词都不相同。
输出格式
-
第一行包含一个整数M,表示打印这N个单词所需操作的最小数目。
-
接下来的M行,每行一个字符,表示你的操作序列,序列的描述方法如下:
添加一个字母,用这个小写字母的自身来表示。
删去一个字母,用
-
表示(减号,ASCII 45)。打印单词,用
P
表示(大写字母P)。
输入输出样例
输入 #1
3
print
the
poem
输出 #1
20
t
h
e
P
-
-
-
p
o
e
m
P
-
-
-
r
i
n
t
P
说明/提示
对于40分的数据,n≤18。
对于所有数据,1≤N≤25000。
这题题眼是要最后留下最长的,所以前面打印的时候应该先打印和最后最长的没关系的。为什么这样?最少操作==最少删除,剩余最长的可以最少删除。所以把最长的在字典树上打好标签,深搜的时候最后搜就可以了。
AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int N = 5000010;
int n;
int ml = 0;
int mi = 0;
string s[25004];
int tr[N][30];
int tot = 0;
int ans;
char le[N];
bool tag[N];
bool ed[N];
string op;
void add(string x){
int pos = 0;
for(int i = 0; x[i]; i++){
int c = x[i] - 'a';
if(!tr[pos][c]){
tr[pos][c] = ++tot;
le[tr[pos][c]] = c + 'a';
}
pos = tr[pos][c];
}
ed[pos] = true;
}
void work(int now){
if(ed[now]){
ans++;
op += 'P';
}
if(ans == n){
cout<<op.size()<<endl;
for(int i = 0; op[i]; i++){
cout<<op[i]<<endl;
}
}
for(int i = 0; i < 26; i++){
if(!tag[tr[now][i]] && tr[now][i]){
op += le[tr[now][i]];
work(tr[now][i]);
op += '-';
}
}
for(int i = 0; i < 26; i++){
if(tag[tr[now][i]] && tr[now][i]){
op += le[tr[now][i]];
work(tr[now][i]);
op += '-';
}
}
}
void Tag(string x){
int pos = 0;
for(int i = 0; x[i]; i++){
int c = x[i] - 'a';
tag[tr[pos][c]] = true;
pos = tr[pos][c];
}
}
int main()
{
ios::sync_with_stdio(false);
cin >> n;
for(int i = 1; i <= n; i++){
cin >> s[i];
if(s[i].size() > ml){
ml = s[i].size();
mi = i;
}
add(s[i]);
}
// mi--;
// int pos = 0;
// for(int i = 0; s[mi][i]; i++){
//
// int c = s[mi][i] - 'a';
// tag[tr[pos][c]] = true;
// pos = tr[pos][c];
// }
Tag(s[mi]);
work(0);
return 0;
}