poj 3415 Common Substrings(后缀数组+单调栈)

http://poj.org/problem?id=3415

Common Substrings
Time Limit: 5000MS   Memory Limit: 65536K
Total Submissions: 5805   Accepted: 1911

Description

A substring of a string T is defined as:

 

T(i, k)=TiTi+1...Ti+k-1, 1≤ii+k-1≤|T|.

 

Given two strings A, B and one integer K, we define S, a set of triples (i, j, k):

 

S = {(i, j, k) | kK, A(i, k)=B(j, k)}.

 

You are to give the value of |S| for specific A, B and K.

Input

The input file contains several blocks of data. For each block, the first line contains one integer K, followed by two lines containing strings A and B, respectively. The input file is ended by K=0.

1 ≤ |A|, |B| ≤ 105 1 ≤ K  min{|A|, |B|} Characters of A and B are all Latin letters.

 

Output

For each case, output an integer |S|.

Sample Input

2
aababaa
abaabaa
1
xx
xx
0

Sample Output

22
5

思路:

【题意】

给定一个数k

在给定两个字符串A,B

求三元组(i,j,k)(表示从A的第i位起和从B的j位起长度为k的字符串相同)的个数

【输入】

多组数据

每组数据第一行为k

接下来两行分别为A、B(长度均不大于100000)

【输出】

对于每组数据,输出一个数,表示三元组的个数

 

后缀数组应用题之一

后缀数组的用法很经典

将两个字符串之间加一个没出现过的字符连接起来

然后求height

对于B的一个后缀,对应每一个A的后缀若他们的公共前缀长为l,若l大于等于k,则会有l-k+1种三元组

这个统计便是本题的难点

如果枚举A的后缀和B的后缀,那么复杂度为n^2,对于本题明显是不可以的

所以要另寻途径

根据论文的提示要使用单调栈,我想到了一种实现

按rank的顺序,统计每一个B的后缀名次之前的A的后缀有关的三元组数量

然后再统计每一个A的后缀名次之前的B的后缀有关的三元组数量

两者之和便是答案

将问题划分为了两个等价的问题

那么完成每一个问题的时候从左到右扫描,每一个只跟已扫描过的有关

这时候便可以动态维护了

首先,与许多后缀数组题目中类似的,这里存在三元组的后缀们是聚集在一起的

按height可以分组

对于每一组从左到右扫描,则需要维护一个栈和一个值

这个栈是栈内元素与当前元素公共前缀长度递增的一个栈,值是若当前当前后缀之前的三元组个数

根据最长公共前缀的性质,rank越相近,则公共前缀长度越大,所以从左到右扫描的后缀与当前后缀的公共前缀长度是递减的

栈内每个元素表示之前有total个后缀与当前元素公共前缀长度为sim,明显,若当前height小于某些栈内元素的sim,则需要修改这些元素和值

据此调整,每个元素最多进出栈一次,复杂度为O(N)

题解部分转自:http://blog.csdn.net/weixinding/article/details/7222882

AC代码:(用数组模拟栈)

  1 #include <iostream>
  2 #include <stdio.h>
  3 #include<string.h>
  4 
  5 #define maxn 200010
  6 
  7 #define cls(x) memset(x, 0, sizeof(x))
  8 
  9 int wa[maxn],wb[maxn],wv[maxn],wss[maxn];
 10 
 11 int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
 12 
 13 //倍增算法
 14 void da(char *r,int *sa,int n,int m)
 15 {
 16      cls(wa);
 17      cls(wb);
 18      cls(wv);
 19      int i,j,p,*x=wa,*y=wb,*t;
 20 
 21      //基数排序
 22      for(i=0;i<m;i++) wss[i]=0;
 23      for(i=0;i<n;i++) wss[x[i]=r[i]]++;
 24      for(i=1;i<m;i++) wss[i]+=wss[i-1];
 25      for(i=n-1;i>=0;i--) sa[--wss[x[i]]]=i;
 26 
 27      // 在第一次排序以后,rank数组中的最大值小于p,所以让m=p。整个倍增算法基本写好,代码大约25行。
 28      for(j=1,p=1;p<n;j*=2,m=p)
 29      {
 30        //接下来进行若干次基数排序,在实现的时候,这里有一个小优化。基数排序要分两次,第一次是对第二关键字排序,第二次是对第一关键字排序。对第二关键字排序的结果实际上可以利用上一次求得的sa直接算出,没有必要再算一次
 31        for(p=0,i=n-j;i<n;i++) y[p++]=i;
 32        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
 33 
 34        //其中变量j是当前字符串的长度,数组y保存的是对第二关键字排序的结果。然后要对第一关键字进行排序,
 35        for(i=0;i<n;i++) wv[i]=x[y[i]];
 36        for(i=0;i<m;i++) wss[i]=0;
 37        for(i=0;i<n;i++) wss[wv[i]]++;
 38        for(i=1;i<m;i++) wss[i]+=wss[i-1];
 39        for(i=n-1;i>=0;i--) sa[--wss[wv[i]]]=y[i];
 40 
 41        //这样便求出了新的sa值。在求出sa后,下一步是计算rank值。
 42        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
 43        x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
 44      }
 45 }
 46 
 47 int rank[maxn],height[maxn];
 48 
 49 //得到height数组:排名相邻的两个后缀的最长公共前缀
 50 void calheight(char *r,int *sa,int n)
 51 {
 52      cls(rank);
 53      cls(height);
 54      int i,j,k=0;
 55      for(i=1;i<n;i++) rank[sa[i]]=i;
 56      for(i=0;i<n;height[rank[i++]]=k)
 57          for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
 58      return;
 59 }
 60 
 61 char ca[maxn];
 62 int sa[maxn];
 63 int N,len;
 64 int minh[maxn];
 65 int stk[maxn],cnt[maxn];
 66 
 67 int main()
 68 {
 69     int i,k;
 70     while (~scanf("%d",&k)&&k)
 71     {
 72         scanf("%s",ca);
 73         len= N = strlen(ca);
 74         ca[N] = '#';
 75         scanf("%s",ca+N+1);
 76         N = strlen(ca);
 77         da(ca, sa, N, 130);
 78         calheight(ca,sa,N);
 79         for(i=1;i<=N;i++)  //将LCP-k+1预处理到height数组中
 80         {
 81             if(height[i]>=k)   height[i]-=k-1;
 82             else   height[i]=0;
 83         }
 84         __int64 ans,temp,size;
 85         ans=temp=0;
 86         int top=-1;
 87         for(i=1;i<=N;i++)
 88         {
 89             for(size=0;top>-1&&stk[top]>height[i];top--)
 90             {
 91                 size+=cnt[top];
 92                 temp+=(height[i]-stk[top])*cnt[top];
 93             }
 94             stk[++top]=height[i];
 95             cnt[top]=size;
 96             if(sa[i-1]<len)
 97             {
 98                 temp+=height[i];
 99                 cnt[top]++;
100             }
101             if(sa[i]>len)
102                 ans+=temp;
103         }
104         temp=0;
105         top=-1;
106         for(i=1;i<=N;i++)
107         {
108             for(size=0;top>-1&&stk[top]>height[i];top--)
109             {
110                 size+=cnt[top];
111                 temp+=(height[i]-stk[top])*cnt[top];
112             }
113             stk[++top]=height[i];
114             cnt[top]=size;
115             if(sa[i-1]>len)
116             {
117                 temp+=height[i];
118                 cnt[top]++;
119             }
120             if(sa[i]<len)
121                 ans+=temp;
122         }
123         printf("%I64d\n",ans);
124     }
125     return 0;
126 }

 

附:

利用栈容器TLE(搞不懂为神马!!!):

<过了两天,终于被我发现为嘛超时了,G++提交AC,C++提交TLE,我了个去啊,坑爹,坑爹啊,目测原因C++容器处理过程耗时太多了>

(C++)TLE代码<G++提交AC>:

  1 #include <iostream>
  2 #include <stdio.h>
  3 #include<string.h>
  4 #include <stack>
  5 
  6 using namespace std;
  7 
  8 #define maxn 200010
  9 
 10 #define cls(x) memset(x, 0, sizeof(x))
 11 
 12 struct Nod
 13 {
 14     int height;
 15     int s;
 16 }node;
 17 
 18 int wa[maxn],wb[maxn],wv[maxn],wss[maxn];
 19 
 20 int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
 21 
 22 //倍增算法
 23 void da(char *r,int *sa,int n,int m)
 24 {
 25      cls(wa);
 26      cls(wb);
 27      cls(wv);
 28      int i,j,p,*x=wa,*y=wb,*t;
 29 
 30      //基数排序
 31      for(i=0;i<m;i++) wss[i]=0;
 32      for(i=0;i<n;i++) wss[x[i]=r[i]]++;
 33      for(i=1;i<m;i++) wss[i]+=wss[i-1];
 34      for(i=n-1;i>=0;i--) sa[--wss[x[i]]]=i;
 35 
 36      // 在第一次排序以后,rank数组中的最大值小于p,所以让m=p。整个倍增算法基本写好,代码大约25行。
 37      for(j=1,p=1;p<n;j*=2,m=p)
 38      {
 39        //接下来进行若干次基数排序,在实现的时候,这里有一个小优化。基数排序要分两次,第一次是对第二关键字排序,第二次是对第一关键字排序。对第二关键字排序的结果实际上可以利用上一次求得的sa直接算出,没有必要再算一次
 40        for(p=0,i=n-j;i<n;i++) y[p++]=i;
 41        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
 42 
 43        //其中变量j是当前字符串的长度,数组y保存的是对第二关键字排序的结果。然后要对第一关键字进行排序,
 44        for(i=0;i<n;i++) wv[i]=x[y[i]];
 45        for(i=0;i<m;i++) wss[i]=0;
 46        for(i=0;i<n;i++) wss[wv[i]]++;
 47        for(i=1;i<m;i++) wss[i]+=wss[i-1];
 48        for(i=n-1;i>=0;i--) sa[--wss[wv[i]]]=y[i];
 49 
 50        //这样便求出了新的sa值。在求出sa后,下一步是计算rank值。
 51        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
 52        x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
 53      }
 54 }
 55 
 56 int rank[maxn],height[maxn];
 57 
 58 //得到height数组:排名相邻的两个后缀的最长公共前缀
 59 void calheight(char *r,int *sa,int n)
 60 {
 61      cls(rank);
 62      cls(height);
 63      int i,j,k=0;
 64      for(i=1;i<n;i++) rank[sa[i]]=i;
 65      for(i=0;i<n;height[rank[i++]]=k)
 66          for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
 67      return;
 68 }
 69 
 70 char ca[maxn];
 71 int sa[maxn];
 72 int N,len;
 73 int minh[maxn];
 74 
 75 int main()
 76 {
 77     int i,k;
 78     while (~scanf("%d",&k)&&k)
 79     {
 80         scanf("%s",ca);
 81         len= N = strlen(ca);
 82         ca[N] = '#';
 83         scanf("%s",ca+N+1);
 84         N = strlen(ca);
 85         da(ca, sa, N, 130);
 86         calheight(ca,sa,N);
 87         for(i=1;i<=N;i++)  //将LCP-k+1预处理到height数组中
 88         {
 89             if(height[i]>=k)   height[i]-=k-1;
 90             else   height[i]=0;
 91         }
 92         stack<Nod> stk;
 93         __int64 ans,temp;
 94         int s=0;
 95         ans=0;
 96         temp=0;
 97         for(i=1;i<=N;i++)  //这里从1遍历到N 等号不能去掉,需要保证pop能彻底
 98         {
 99             s=0;
100             while(stk.size()>0&&height[i]<stk.top().height)
101             {
102                 s+=stk.top().s;
103                 temp+=(height[i]-stk.top().height)*stk.top().s;
104                 stk.pop();
105             }
106             if(sa[i-1]<len)
107             {
108                 temp+=height[i];
109                 s++;
110             }
111             if(sa[i]>len)   ans+=temp;
112             node.height = height[i];
113             node.s = s;
114             stk.push(node);
115         }
116         while(!stk.empty())  stk.pop();
117         temp=0;
118         for(i=1;i<=N;i++)
119         {
120             s=0;
121             while(stk.size()>0&&height[i]<stk.top().height)
122             {
123                 s+=stk.top().s;
124                 temp+=(height[i]-stk.top().height)*stk.top().s;
125                 stk.pop();
126             }
127             if(sa[i-1]>len)
128             {
129                 temp+=height[i];
130                 s++;
131             }
132             if(sa[i]<len)   ans+=temp;
133             node.height = height[i];
134             node.s = s;
135             stk.push(node);
136         }
137         while(!stk.empty())  stk.pop();
138         printf("%I64d\n",ans);
139     }
140     return 0;
141 }

 

 

posted @ 2013-07-24 19:55  crazy_apple  阅读(392)  评论(0编辑  收藏  举报