描述
xx学习了Trie树后,向你问了一个问题,给定一个字符串集合S={str1, str2,
…,strn}和一个字符串s,在s的后面接尽量少的字符,使其属于集合S。当然如果s本身就属于S,s就是答案。
输入
第一行一个正整数T(T <= 10),表示有T组数据。 每组数据输入格式如下:
第一行为一个正整数N(0 < N < 20000),表示字符串集合内的字符串数。 接下来N行,每行一个字符串,表示集合中的串。
接下来一行是一个正整数Q(0 < Q < 200),表示有Q次询问。 接下来Q行,每行一个字符串str。
所有字符串均由小写英文字母组成,且1 <= 长度 <= 20。
输出
每组数据输出格式如下: 先输出“Case x:”,x表示是第几组数据,然后输出一个换行。
接下来输出Q行,对于每次询问,如果str能够添加字符使其属于字符串集合,则输出长度最小的(即str添加字符后构成的串),有多个满足条件的话输出字典序最小的。否则输出-1。
思路与解法
构造字典树的时候动态更新记录以下信息:
1. 每个子节点后连接的最短单词的长度
2. 当前节点是否是某单词的结尾
3. 当前节点后所连接的最短或字典序最小的单词的子节点是哪个
查询时,若存在满足条件的单词,则根据查询后停留的节点信息,循环补全单词即可。
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define M 400001
#define INF 100
struct TT{
int l[27]; // l[0]~l[25]记录以各子节点为起点存在的最短单词长度, l[26]记录l[0]~l[25]中最短长度的下标值。初始为INF
struct TT *p[27]; // p[0]~p[25]记录个子节点地址,p[26]记录是否存在以该节点结尾的单词,若没有则为0
} *tt, mem[M]; // mem[M]预分配内存
int inx;
struct TT * qmalloc(){ // 自定义节点内存分配函数,初始化节点。
struct TT *p = mem + inx++;
for (int i = 0; i < 27; p->p[i++] = 0)
p->l[i] = INF;
p->l[26] = 0;
return p;
}
void add(const char *s){ // 插入字典树
struct TT *p;
int l = strlen(s);
for (p = tt; *s; l--){ // l 维持插入单词长度
int i = *s++ - 'a';
if(!p->p[i]) p->p[i] = qmalloc();
if (p->l[i] > l) p->l[i] = l; // 当插入单词长度小于当前节点对应子节点最小长度时更新
if (p->l[p->l[26]] > l || p->l[p->l[26]] == l && p->l[26] > i) // 当插入单词长度小于当前节点最小长度或字典序更小时更新
p->l[26] = i;
p = p->p[i];
}
p->p[26] = p; // 标记单词结束的节点
}
void ff(const char * s){
struct TT *p;
const char *tmps = s;
for (p = tt; *s; p = p->p[*s++ - 'a'])
if (!p->p[*s - 'a']){ // 没有查到时直接返回
printf("-1\n");
return;
}
// 首先输出原单词,根据l[26]中记录的最短子节点循环补全字母,直到当前节点存在结束标记
for (printf(tmps); !p->p[26]; p = p->p[p->l[26]])
printf("%c", p->l[26] + 'a');
puts("");
};
int main(void)
{
int n, T, t;
char ss[30];
scanf("%d", &T);
for (t = 1; T--; t++){
inx = 0; // 初始化预分配内存
tt = qmalloc(); // 初始化字典树
for (scanf("%d", &n); n--; add(ss))
scanf("%s", ss);
printf("Case %d:\n", t);
for (scanf("%d", &n); n--; ff(ss))
scanf("%s", ss);
}
return 0;
}