POJ 3415 Common Substrings

本篇是罗穗骞《后缀数组——处理字符串的有力工具》的读书笔记。

解题思路:

  先把\(A, B\)两个字符串连接起来,中间用一个非拉丁字母隔开,组成一个大字符串。预处理出该字符串的后缀数组和高度数组。

  扫描第一次高度数组,维护第一个单调栈,记录 \(A\) 的 “高度值”\(lcp\)(栈顶最小)和对应这个 \(lcp\) 的 \(A\) 的后缀个数 \(size\). 当让一个新的 \(A\) 的后缀的  \(lcp\) 入栈时,如果 \(lcp\) 小于栈顶,则直接入栈,\(size\) 为 1;否则要把栈顶元素的 \(lcp\) 削减到与将要入栈的 \(lcp\) 一样,合并到将要入栈的元素中(当然,\(size\) 也要合并),\(contribution\) 值也要减去栈顶元素的 \(lcp \times size\)。这个 \(contribution\) 值,记录的是当前的栈所维护的 \(A\) 的后缀遇到相应的 \(B\) 的后缀时能对答案做出的 “贡献值”。当扫描到 \(B\) 的后缀时,更新栈顶元素和 \(contribution\),使栈顶元素的 \(lcp\) 等于 \(B\) 的后缀所对应的 \(lcp\). 在最终答案中加上最新的 \(contribution\). 如此便可记录下 \(B\) 的所有的 \(lcp \ge K\) 的后缀与其前面的 \(A\) 的后缀所能产生的长度不小于 K 的子串的个数。

  扫描第一遍高度数组,维护一个与上述类似的关于 \(B\) 的单调栈,记录 \(A\) 的所有的 \(lcp \ge K\) 的后缀与其前面的 \(B\) 的后缀所能产生的长度不小于 K 的子串的个数。

  两遍所得的总个数之和即为答案。

AC代码:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 const int maxn = 2e5+10;
 7 
 8 int len,tk;
 9 int Rank[maxn],tmp[maxn];
10 char S[maxn];
11 int sa[maxn],lcp[maxn];
12 bool compare_sa(int i,int j){
13     if(Rank[i]!=Rank[j])    return Rank[i]<Rank[j];
14     else{
15         int ri=i+tk<=len?Rank[i+tk]:-1;
16         int rj=j+tk<=len?Rank[j+tk]:-1;
17         return ri<rj;
18     }
19 }
20 void construct_sa(){
21     for(int i=0;i<=len;i++){
22         sa[i]=i;
23         Rank[i]=i<len?S[i]:-1;
24     }
25     for(tk=1;tk<=len;tk*=2){
26         sort(sa,sa+len+1,compare_sa);
27         tmp[sa[0]]=0;
28         for(int i=1;i<=len;i++){
29             tmp[sa[i]]=tmp[sa[i-1]]+(compare_sa(sa[i-1],sa[i])?1:0);
30         }
31         for(int i=0;i<=len;i++){
32             Rank[i]=tmp[i];
33         }
34     }
35 }
36 void construct_lcp(){
37     int h=0;
38     lcp[0]=0;
39     for(int i=0;i<len;i++){
40         int j=sa[Rank[i]-1];
41         if(h>0) h--;
42         for(;j+h<len&&i+h<len;h++){
43             if(S[j+h]!=S[i+h])  break;
44         }
45         lcp[Rank[i]-1]=h;
46     }
47 }
48 int K,len1,len2;
49 char A[maxn],B[maxn];
50 ll Stack[2][maxn]; //Stack[0] 记录lcp; Stack[1] 记录个数。
51 ll solve(bool flag){  //1,栈维护 A 的后缀;0,栈维护 B 的后缀。
52     ll ret=0;
53     int top=0;  //top 指向栈顶
54     ll contribution=0;
55     for(int i=0;i<len;i++){
56         if(lcp[i]<K){
57             top=contribution=0; //清空栈
58         }
59         else{
60             int Size=0;
61             if((sa[i]<len1&&flag)||(sa[i]>len1&&!flag)){    //说明这个元素需要入栈
62                 Size=1;
63                 contribution+=(lcp[i]-K+1);
64             }
65             while(top>0&&lcp[i]<=Stack[0][top-1]){
66                 top--;
67                 contribution-=Stack[1][top]*(Stack[0][top]-lcp[i]);
68                 Size+=Stack[1][top];
69             }
70             if(Size){
71                 Stack[0][top]=lcp[i];
72                 Stack[1][top]=Size;
73                 top++;
74             }
75             if((sa[i+1]>len1&&flag)||(sa[i+1]<len1&&!flag))
76                 ret+=contribution;
77         }
78     }
79     return ret;
80 }
81 int main(){
82     while(scanf("%d",&K)==1&&K){
83         scanf("%s",A);
84         scanf("%s",B);
85         len1=strlen(A),len2=strlen(B);
86         len=len1+len2+1;
87         int ind=0;
88         for(int i=0;i<len1;i++,ind++)   S[ind]=A[i];
89         S[ind++]='#';
90         for(int i=0;i<len2;i++,ind++)   S[ind]=B[i];
91         construct_sa();
92         construct_lcp();
93         printf("%lld\n",solve(1)+solve(0));
94     }
95     return 0;
96 }

 

posted @ 2017-12-06 23:01  Blogggggg  阅读(148)  评论(0编辑  收藏  举报