题目: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 }
字典树

相关文章: