本题数据略大,显然不能用普通的儿子节点法建立trie,因此采用左儿子右兄弟表示法建树,其实就是二叉树表示法,本质就是把原来的26叉树转成2叉树,时间复杂度常数增大,空间复杂度尽可能减小
我们知道26叉树中,如果数据不那么变态,应该是大部分节点都是浪费掉的,而使用二叉树建树,能充分利用空间,缺点就是 原来访问某个节点的子节点 如果查询子节点中有没‘a’字符,我们只需要访问ch[u][0]即可,O(1),而在二叉树中,我们得遍历他所有的儿子节点,最差的情况是O(26)
当然,如果数据出到了最极端的情况,那么两种方法就没什么区别了。
本题按照LRJ的做法 是把单词包括最后的‘\0’都插入字典树了。。其实也可以不插入。就是要判断一下,我2种方法都写了一下,时间差不多
分别是 512ms,492ms
插入‘\0’:
// UVa11732 strcmp() Anyone? // Yuhong Liu #include<cstring> #include<vector> #include<cstdio> #include<algorithm> using namespace std; const int maxnode = 4005 * 1005 + 10; int ok,ok2; __int64 ans; // 答案 // 字母表为全体小写字母的Trie struct Trie { int head[maxnode]; // head[i]为第i个结点的左儿子编号 int next[maxnode]; // next[i]为第i个结点的右兄弟编号 char ch[maxnode]; // ch[i]为第i个结点上的字符 int tot[maxnode]; // tot[i]为第i个结点为根的子树包含的叶结点总数 int sz; // 结点总数 void clear() { sz = 1; tot[0] = head[0] = next[0] = 0; } // 初始时只有一个根结点 // 插入字符串s(包括最后的'\0'),沿途更新tot void insert(const char *s) { int u = 0, v, n = strlen(s); tot[0]++; for(int i = 0; i <=n; i++) { // 找字符a[i] bool found = false; for(v = head[u]; v != 0; v = next[v]) { if(ch[v] == s[i]) { // 找到了 found = true; break; } } if(!found) { v = sz++; // 新建结点 tot[v] = 0; ch[v] = s[i]; next[v] = head[u]; head[u] = v; // 插入到链表的首部 head[v] = 0; } tot[v]++; u = v; } } // 统计 __int64 cal(int depth,int u) { if (ch[u]=='\0'&&u!=0) { ans+=tot[u]*(tot[u]-1)/2 *(2*depth); return 0; } int v,i; for(v = head[u]; v != 0; v = next[v]) { if (tot[v]==tot[u]) { cal(depth+1,v); return 0; } else break; } int cun=0; int ttt[70]; int vv[70]; __int64 tmp=0; for(v = head[u]; v != 0; v = next[v]) { ttt[++cun]=tot[v]; tmp+=tot[v]; vv[cun]=v; } //之前没把'\0'插入字典树,导致计算时要对两个相同字符串特殊处理(完全相同长度为n的2个串比较次数为2*(n+1)) __int64 tmp2=0; for (i=1;i<=cun;i++) { tmp-=ttt[i]; tmp2+=((__int64)ttt[i])*tmp; } tmp2*=(2*depth+1); ans+=tmp2; for (i=1;i<=cun;i++) { if (tot[vv[i]]==1) continue; cal(depth+1,vv[i]); } } }; const int maxl = 1000 + 10; // 每个单词最大长度 int n; char word[maxl]; Trie trie; int main() { int i; int kase = 1; while(scanf("%d", &n) == 1 && n) { trie.clear(); ok=0; for( i = 1; i <=n; i++) { scanf("%s", word); trie.insert(word); } ans=0; trie.cal(0,0); printf("Case %d: %lld\n", kase++, ans); } return 0; }
// UVa11732 strcmp() Anyone? // Yuhong Liu #include<cstring> #include<vector> #include<cstdio> #include<algorithm> using namespace std; const int maxnode = 4005 * 1005 + 10; int ok,ok2; long long ans; // 答案 // 字母表为全体小写字母的Trie struct Trie { int head[maxnode]; // head[i]为第i个结点的左儿子编号 int next[maxnode]; // next[i]为第i个结点的右兄弟编号 char ch[maxnode]; // ch[i]为第i个结点上的字符 int tot[maxnode]; // tot[i]为第i个结点为根的子树包含的叶结点总数 int sz; // 结点总数 void clear() { sz = 1; tot[0] = head[0] = next[0] = 0; } // 初始时只有一个根结点 // 插入字符串s(包括最后的'\0'),沿途更新tot void insert(const char *s) { int u = 0, v, n = strlen(s); tot[0]++; for(int i = 0; i < n; i++) { // 找字符a[i] bool found = false; for(v = head[u]; v != 0; v = next[v]) { if(ch[v] == s[i]) { // 找到了 found = true; break; } } if(!found) { v = sz++; // 新建结点 tot[v] = 0; ch[v] = s[i]; next[v] = head[u]; head[u] = v; // 插入到链表的首部 head[v] = 0; } tot[v]++; u = v; } } // 统计 long long cal(int depth,int u) { int v,i; for(v = head[u]; v != 0; v = next[v]) { if (tot[v]==tot[u]) { cal(depth+1,v); return 0; } else break; } int cun=0; int ttt[70]; int vv[70]; long long tmp=0; for(v = head[u]; v != 0; v = next[v]) { ttt[++cun]=tot[v]; tmp+=tot[v]; vv[cun]=v; } int temp=0; if (tmp<tot[u]) { temp=(tot[u]-tmp); //必须要把相同的合并在一起,否则导致run error,因为一种字符只能占一个位置 ttt[++cun]=temp; vv[cun]=-1; } tmp=tot[u]; long long tmp2=0; for (i=1;i<=cun-1;i++) { tmp-=ttt[i]; tmp2+=((long long )ttt[i])*tmp; } tmp2*=(2*depth+1); ans+=tmp2; if (temp) { ans+=((temp-1)*temp)/2*(2*depth+2); } for (i=1;i<=cun;i++) { if (vv[i]==-1)continue; if (tot[vv[i]]==1) continue; cal(depth+1,vv[i]); } } }; const int maxl = 1000 + 10; // 每个单词最大长度 int n; char word[maxl]; Trie trie; int main() { int i; int kase = 1; while(scanf("%d", &n) == 1 && n) { trie.clear(); ok=0; for( i = 1; i <=n; i++) { scanf("%s", word); trie.insert(word); } ans=0; trie.cal(0,0); printf("Case %d: %lld\n", kase++, ans); } return 0; }