字典树模板
2013-04-23 22:02
232 查看
struct Trie //字典树结构 { Trie *child[26]; int num; //子节点数 bool end; //判断该字母是否为某个单词的结束字母 Trie() //构造函数 { num=0; end=0; memset(child,0,sizeof(child)); } }; Trie *root,*s,*lrelia; void Create(char *str) //插入单词 { s=root; int i=0; while(str[i]) { int id=str[i]-'a'; if(s->child[id]==0) //如果该字母还没有出现在字典中 { s->child[id]=new Trie; s->num++; //子节点数+1 s=s->child[id]; } else { s=s->child[id]; } i++; } s->end=1;//标记单词的最后一个字母 } double Search(char *str) { s=root; double len=1; //因为第一个字母必须得敲击,所以len赋值1, int o=strlen(str); /* 既然前一个字母的子节点数决定下一个字母是否要敲击, 我们只需要遍历第1个到len-1个字母就可以了。 */ for(int i=0;i<o-1;++i) { int id=str[i]-'a'; s=s->child[id]; if(s->num>1||s->end==1) len++; } return len; }
上面的模板是动态的,优点:节约空间,代码简洁,缺点:太慢了,比如POJ3630 Phone list,动态模板和静态模板相差了10倍!
静态模板:
#define mod 20071027 #define MAXN 400005 int dp[5555],num,re,p; char a[300005]; struct Trie { int child[26]; bool end; Trie() { end=0; memset(child,0,sizeof(child)); } void set() { end=0; memset(child,0,sizeof(child)); } }t[MAXN]; void Create(char *s) { int root=0,i=0,id; while(s[i]) { id=s[i]-'a'; if(t[root].child[id]==0) t[root].child[id]=p++; root=t[root].child[id]; i++; } t[root].end=1; } void Search(char *s) { int root=0,i=0,id,len=strlen(s); for(i=0;i<len;++i) { id=s[i]-'a'; if(t[root].child[id]) { root=t[root].child[id]; if(t[root].end) { num+=dp[re+i+1]; } } else break; } }
更高效的静态模板:
#define mod 20071027 #define MAXN 300005 int dp[MAXN]; char a[MAXN]; struct Trie { int ch[MAXN][26]; bool val[MAXN]; int sz; void clear() { sz=1; memset(ch[0],0,sizeof(ch[0])); } void Create(char *s) { int u=0,n=strlen(s); //u相当于根节点 for(int i=0;i<n;++i) { int c=s[i]-'a'; if(!ch[u][c]) { memset(ch[sz],0,sizeof(ch[sz])); val[sz]=0; ch[u][c]=sz++; } u=ch[u][c]; } val[u]=1; } void Search(char *s,int pos) { int i=0,u=0; while(s[i]!=0&&ch[u][s[i]-'a']) { u=ch[u][s[i]-'a']; if(val[u]) { dp[pos]=dp[pos+i+1]?dp[pos]+dp[pos+i+1]:dp[pos]; if(dp[pos]>=mod) dp[pos]-=mod; } ++i; } } }tr;
o(︶︿︶)o GO DIE,居然还有前向星式的模板,一维的
前向星式图解:
/* 题意: 给出n个字符串, 计算两两比较的次数. 每次比较都需要比较(str1[i] == str2[i])和 (str1[i] == '\0'各一次). 点评: 将N个字符串插入前缀树,‘\0’也插入,这样就能区分字符串的终结点 S1与S2的共同前缀S,则比较次数为len(S)*2+1 但S1与S2相同,则比较次数为 (len(S1)+1)*2 (注意:这时连'\0’都算进去了噢~,因为适用性,所以模板最好是用一维的前向星式的 */ #include <cstdio> #include <iostream> #include <cstring> using namespace std; #define MAX 1005 const int maxnode = 4001*1000+5; const int sigma_size = 26; typedef long long ll; struct Trie { int first[maxnode], next[maxnode]; //前向星式子建图还记得么,亲? int total[maxnode], ch[maxnode]; //total[i]记录每个节点被遍历的的次数,ch[i]记录字符串的值的 int sz; //记录ch的坐标的 void clear() { sz = 1; total[0] = first[0] = next[0] = 0; } void insert(const char *s) { int u = 0, v, n = strlen(s); total[u]++; for(int i = 0; i <= n; ++i) { bool flag = false; for(v = first[u]; v != 0; v = next[v]) //前向星式遍历 { if(ch[v] == s[i]) { flag = true; break; } } if( !flag ) { v = sz++; total[v] = 0; //初始化 ch[v] = s[i]; next[v] = first[u]; first[u] = v; first[v] = 0; } u = v; total[u]++; } } void dfs(int depth, int u, ll &ans) { if( first[u] == 0 ) ans += total[u]*(total[u]-1)*depth; //如果下面没有节点了,那么ans*=total[u]*(total[u]-1)*depth else { int sum = 0; for(int v = first[u]; v != 0; v = next[v]) sum += total[v]*(total[u]-total[v]); //否则计算比较次数,当前节点-相同的分支的节点的数量 ans += (sum/2*(2*depth+1)); for(int v = first[u]; v != 0; v = next[v]) dfs(depth+1, v, ans); } } }; int n; char str[MAX]; Trie tr; int main() { int t = 1; while(~scanf("%d", &n)) { if(n == 0) break; tr.clear(); for(int i = 0; i < n; ++i) { scanf("%s", str); tr.insert(str); } ll ans = 0; tr.dfs(0, 0, ans); printf("Case %d: %lld\n", t++, ans); } return 0; }