题目:Boring counting
链接:http://acm.hdu.edu.cn/showproblem.php?pid=3518
题意:给一个字符串,问有多少子串出现过两次以上,重叠不能算两次,比如ababa,aba只出现一次。
思路:
网上搜的题解估计大部分都是后缀数组,但字典树+优化是可以解决该问题的。
字典树解决这题难点就是内存,先不考虑内存,那么可以遍历起始点,然后添加入字典树,比如现在abab要添加进字典树,如果原本已经存在abab,并且两个不重叠,那么ans++,同时将abab标记掉,如果不存在,记录此时的下标以便等会判断是否重叠。(很简单的思路。)
现在解决内存,可以计算,如果要通过内存限制,字典树节点只能27万左右。但如果只设置这么大,最后会超出,会RE(G++好像会显示TLE),可以想象,字典树上很多节点的next[26]都是-1,浪费空间,因此可以把next[26]换成vector,动态申请,查找时多花一点时间遍历,但内存大大减小。
---------------------------------------------------------------------------------
下面是后缀数组解决该问题的方法:
首先要明白后缀数组里几个数组的用法,这里不详述了。
首先,我们可以遍历满足要求的字串的长度len,从1 到ls/2,然后遍历一遍height数组,height[i]表示排名第i 的后缀和排名第i-1 的后缀的最长公共前缀长度,那么如果height[i]>=len,这就有可能是答案了,只要不重叠就可以了,重叠可以用sa数组判断,可以找出最左边的下标记为l,最右边的下标记为r,只要l+len<=r就可以了,注意,height<len以后就是另外的字符串了。
AC代码:
1 #include<stdio.h> 2 #include<string.h> 3 #include<map> 4 #include<vector> 5 using namespace std; 6 struct Node 7 { 8 int val; 9 map<char,int> next; 10 }v[1000010]; 11 int vNum; 12 int ans; 13 void add(char *s,int start) 14 { 15 int p = 0; 16 for(int i=start;s[i];i++) 17 { 18 int t = v[p].next[s[i]]; 19 if(t!=0) p = t; 20 else 21 { 22 v[vNum].val=-1; 23 v[vNum].next.clear(); 24 v[p].next[s[i]]=vNum++; 25 p=vNum-1; 26 } 27 if(v[p].val!=-1) 28 { 29 if(v[p].val!=-2 && v[p].val<start) 30 { 31 ans++; 32 v[p].val=-2; 33 } 34 } 35 else v[p].val = i; 36 } 37 } 38 char s[1010]; 39 int main() 40 { 41 while(~scanf("%s",s)) 42 { 43 if(s[0]=='#') break; 44 v[0].val=-1; 45 for(int i=0;i<26;i++) v[0].next.clear(); 46 vNum=1; 47 ans=0; 48 for(int i=0;s[i];i++) 49 { 50 add(s,i); 51 } 52 printf("%d\n",ans); 53 } 54 return 0; 55 }