描述
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;
}