刘汝佳新书---------训练指南
题意:求N个字符串两两比较,共比较了多少次?
分析:将N个字符串插入前缀树,‘\0’也插入,这样就能区分字符串的终结点(以前我们都没插入)。这个很巧妙,不需要标识点标识字符终结点了,Orzz大牛!
这样我们就能从前缀树中算出次数。
字符串S1,S2的比较分这几种情况
S1与S2的共同前缀S,则比较次数为len(S)*2+1
但S1相同与S2,则比较次数为 len(S1+1)*2
// File Name: 11732.cpp // Author: zlbing // Created Time: 2013/3/16 13:28:15 #include<iostream> #include<string> #include<algorithm> #include<cstdlib> #include<cstdio> #include<set> #include<map> #include<vector> #include<cstring> #include<stack> #include<cmath> #include<queue> using namespace std; #define CL(x,v); memset(x,v,sizeof(x)); #define INF 0x3f3f3f3f #define LL long long #define REP(i,n) for(int i=0;i<n;i++) #define REP1(i,n) for(int i=1;i<n+1;i++) const int maxnode = 4000 * 1000 + 10; const int sigma_size = 62; // 字母表为全体小写字母的Trie struct Trie { int ch[maxnode]; int val[maxnode]; int mark[maxnode]; int head[maxnode]; int flow[maxnode]; int sz; // 结点总数 void clear() { sz = 1; mark[0]=head[0]=flow[0]=0; } // 初始时只有一个根结点 int idx(char c) { if(c>='0'&&c<='9')return c-'0'; else if(c>='A'&&c<='Z')return c-'A'+10; else if(c>='a'&&c<='z')return c-'a'+36; else return 63; } // 字符c的编号 // 插入字符串s,附加信息为v。注意v必须非0,因为0代表“本结点不是单词结点” void insert(const char *s) { int u = 0, n = strlen(s); int v; mark[0]++; for(int i = 0; i <=n; i++) { int c = idx(s[i]); bool found=false; for(v=head[u];v!=0;v=flow[v]){ if(ch[v]==c){ found=true;break; } } if(!found) { // 结点不存在 val[sz] = 0; // 中间结点的附加信息为0 mark[sz]=0; v=sz++; // 新建结点 ch[v]=c; flow[v]=head[u]; head[u]=v; head[v]=0; } u =v; // 往下走 mark[u]++; } val[u]++; // 字符串的最后一个字符的附加信息为v } /*一开始比较弱的比较方法。。。冏*/ LL count(const char *s){ int u=0,n=strlen(s); int c=idx(s[0]); int v; for(v=head[u];v!=0;v=flow[v]){ if(ch[v]==c){ break; } } int k=v; LL ans=0; LL t=0; for(v=head[u];v!=0;v=flow[v]){ if(v!=k)t+=mark[v]; } ans+=t; for(int i=0;i<n-1;i++){ int c=idx(s[i]); for(v=head[u];v!=0;v=flow[v]) if(ch[v]==c)break; u=v; c=idx(s[i+1]); for(v=head[u];v!=0;v=flow[v]) if(ch[v]==c)break; int next=v; if(val[u]){ ans+=(i+1)*2*val[u]; } if(mark[next]!=mark[u]){ ans+=(i*2+1)*(mark[u]-mark[next]); } } c=idx(s[n-1]); for(v=head[u];v!=0;v=flow[v]) if(ch[v]==c)break; u=v; t=0; for(v=head[u];v!=0;v=flow[v]) t+=mark[v]; if(t) ans+=t*(n*2)+1; ans+=(mark[u]-t-1)*(n); return ans; } LL dfs(int depth,int u){ LL ans=0; if(head[u]==0){ ans+=mark[u]*(mark[u]-1)*depth; } else{ LL sum=0; for(int v=head[u];v!=0;v=flow[v]){ sum+=mark[v]*(mark[u]-mark[v]); } ans+=sum/2*(2*depth+1); for(int v=head[u];v!=0;v=flow[v]){ ans+=dfs(depth+1,v); } } return ans; } }; const int stringnode=4011; const int stringlen=1000+10; char str[stringnode][stringlen]; Trie solver; void out(LL a) { if(a < 0) {putchar('-'); a = -a;} if(a >= 10)out(a / 10); putchar(a % 10 + '0'); } int in() { int flag = 1; char ch; int a = 0; while((ch = getchar()) == ' ' || ch == '\n'); if(ch == '-') flag = -1; else a += ch - '0'; while((ch = getchar()) != ' ' && ch != '\n') { a *= 10; a += ch - '0'; } return flag * a; } int main(){ int n; int cas=0; while(n=in()) { if(!n)break; printf("Case %d: ",++cas); REP(i,n)scanf("%s",str[i]); solver.clear(); LL ans=0; REP(i,n){ solver.insert(str[i]); // ans+=solver.count(str[i]); } ans=solver.dfs(0,0); // printf("%lld\n",ans); out(ans); putchar('\n'); } return 0; }
// UVa11732 strcmp() Anyone? // Rujia Liu #include<cstring> #include<vector> using namespace std; const int maxnode = 4000 * 1000 + 10; const int sigma_size = 26; // 字母表为全体小写字母的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; // 结点总数 long long ans; // 答案 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; } u = v; tot[u]++; } } // 统计LCP=u的所有单词两两的比较次数之和 void dfs(int depth, int u) { if(head[u] == 0) // 叶结点 ans += tot[u] * (tot[u] - 1) * depth; else { int sum = 0; for(int v = head[u]; v != 0; v = next[v]) sum += tot[v] * (tot[u] - tot[v]); // 子树v中选一个串,其他子树中再选一个 ans += sum / 2 * (2 * depth + 1); // 除以2是每种选法统计了两次 for(int v = head[u]; v != 0; v = next[v]) dfs(depth+1, v); } } // 统计 long long count() { ans = 0; dfs(0, 0); return ans; } }; #include<cstdio> const int maxl = 1000 + 10; // 每个单词最大长度 int n; char word[maxl]; Trie trie; int main() { int kase = 1; while(scanf("%d", &n) == 1 && n) { trie.clear(); for(int i = 0; i < n; i++) { scanf("%s", word); trie.insert(word); } printf("Case %d: %lld\n", kase++, trie.count()); } return 0; }