题目链接:https://vjudge.net/problem/UVA-11732
知识点: 字典树、儿子兄弟表示法
解题思路:
首先,为每一个字符的末尾添加一个 '$',因为有一种特殊情况:如果两个字符串(假设长度为 len )完全相同,则比较次数是 (2 len + 2) ,因为两个字符串末尾的 '\0' 也是相同的。
用字典树维护字符串集。当插入字符串 \(S\) 中的一个字符时,算出字典树已有的字符串中跟 \(S\) 直到上一个字符都是相同的字符串数 \(last\),以及插入这个字符后仍然跟 \(S\) 相同的字符串数 \(news\),则对答案的贡献即为 \((news \times 2 + last - news)\).
AC代码:
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 typedef long long ll; 5 const int maxn = 4000004; 6 7 char ch[maxn]; //保存结点字符 8 int son[maxn], bro[maxn]; //son是链表头, bro[u]表示u的下一个兄弟结点 9 int nds; //nds是下一个可以分配的结点 10 int cnt[maxn]; //cnt[x]表示经过结点x的字符串数 11 12 ll add(char *str) { 13 int len = strlen(str); 14 str[len] = '$', str[len + 1] = '\0'; 15 ll ret = 0; 16 //*********************************************** 17 //儿子-兄弟表示法(其实就是结合了链表的字典树,目的是节省空间) 18 int now = 0; 19 int last = cnt[now]; 20 for (int i = 0; i <= len; i++) { 21 int v; 22 for (v = son[now]; v; v = bro[v]) { 23 if (ch[v] == str[i]) break; 24 } 25 if (!v) { 26 bro[nds] = son[now]; 27 ch[nds] = str[i]; 28 cnt[nds] = son[nds] = 0; 29 v = son[now] = nds++; 30 } 31 now = v; 32 //************************************************ 33 int news = cnt[now]; 34 ret += news * 2; 35 ret += (last - news); 36 last = cnt[now]; 37 cnt[now]++; 38 } 39 return ret; 40 } 41 char str[4005]; 42 int main() { 43 // freopen("in.txt","r",stdin); 44 int N, kase = 1; 45 while (scanf("%d", &N) == 1 && N) { 46 nds = 1, cnt[0] = son[0] = bro[0] = 0; //初始化 47 ll ans = 0; 48 while (N--) { 49 scanf("%s", str); 50 ans += add(str); 51 cnt[0]++; 52 } 53 printf("Case %d: %lld\n", kase++, ans); 54 } 55 return 0; 56 }