我的做法是先建字典树,统计每个结点出现次数和相同字符串个数,每个结点对答案的贡献就是2*C(次数,2),然后再分别讨论相同字符串和不同字符串对答案的贡献。
另外这题主要就是Trie树的孩子兄弟表示法:
因为数据范围,4000个字符串每个字符串长度1000且字符的范围有62个,用孩子链表表示法最坏,4000*1000*62,再*4Byte,近1GB的内存,太大了,有许多空指针浪费空间。
所以需要用孩子兄弟表示法压缩字典树,4000*1000*2,左孩子右兄弟,用时间换空间。
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 char key[4100000]; 5 int ch[4100000][2],cnt[4100000],flag[4100000],tn; 6 void insert(char *s){ 7 int x=0; 8 for(int i=0; s[i]; ++i){ 9 if(ch[x][0]==0){ 10 ch[x][0]=++tn; 11 x=ch[x][0]; 12 key[x]=s[i]; 13 ++cnt[x]; 14 continue; 15 } 16 for(x=ch[x][0]; key[x]!=s[i] && ch[x][1]; x=ch[x][1]); 17 if(key[x]!=s[i]){ 18 ch[x][1]=++tn; 19 x=ch[x][1]; 20 key[x]=s[i]; 21 ++cnt[x]; 22 }else ++cnt[x]; 23 } 24 ++flag[x]; 25 } 26 27 int main(){ 28 int t=0,n; 29 char str[1111]; 30 while(~scanf("%d",&n) && n){ 31 tn=0; 32 memset(key,0,sizeof(key)); 33 memset(ch,0,sizeof(ch)); 34 memset(cnt,0,sizeof(cnt)); 35 memset(flag,0,sizeof(flag)); 36 while(n--){ 37 scanf("%s",str); 38 insert(str); 39 } 40 long long res=0; 41 int tot=0,sn=0; 42 for(int i=1; i<=tn; ++i){ 43 res+=(cnt[i]-1)*cnt[i]; 44 if(flag[i]){ 45 res+=(flag[i]-1)*flag[i]; 46 tot+=flag[i]; 47 } 48 } 49 long long tmp=0; 50 for(int i=1; i<=tn; ++i){ 51 if(flag[i]) tmp+=(tot-flag[i])*flag[i]; 52 } 53 res+=tmp>>1; 54 printf("Case %d: %lld ",++t,res); 55 } 56 return 0; 57 }