http://poj.org/problem?id=3415
Time Limit: 5000MS | Memory Limit: 65536K | |
Total Submissions: 5805 | Accepted: 1911 |
Description
A substring of a string T is defined as:
Given two strings A, B and one integer K, we define S, a set of triples (i, 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 ",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 ",ans); 139 } 140 return 0; 141 }