POJ 3415 后缀数组+单调栈

 题目大意:

给定A,B两种字符串,问他们当中的长度大于k的公共子串的个数有多少个

这道题目本身理解不难,将两个字符串合并后求出它的后缀数组

然后利用后缀数组求解答案

这里一开始看题解说要用栈的思想,觉得很麻烦就不做了,后来在比赛中又遇到就后悔了,到今天看了很久才算看懂

首先建一个栈,从栈底到栈顶都保证是单调递增的

我们用一个tot记录当前栈中所有项和一个刚进入的子串匹配所能得到的总的子串的数目(当然前提是,当前进入的子串height值比栈顶还大,那么和栈中任意一个子串匹配都保持当前栈中记录的那时候入栈的height值)

但是若height不比栈顶大,说明从栈顶开始到刚好比它小的这一段tot有多加的部分,这部分就是height值多出来的那块,然后把这部分都视作height值为当前的height值,因为后面子串进入,它的height值总是取决于那段区间的最小值,所以不会产生影响,这样就可以把所有比当前height大的都弹出栈,这样就达到了O(n)的复杂度

这里用q[][]手写栈

q[i][0]表示栈中第i号元素记录时候的height值,q[i][1]表示在这个height值上覆盖了q[i][1]个子串

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <iostream>
  4 using namespace std;
  5 #define INF 0x3f3f3f3f
  6 #define ll long long
  7 const int MAXN = 2*100010;
  8 int sa[MAXN] , rank[MAXN] , height[MAXN];
  9 int wa[MAXN] , wb[MAXN] , wsf[MAXN] , wv[MAXN];
 10 int a[MAXN] , k;
 11 char str1[MAXN] , str2[MAXN];
 12 int q[MAXN][2];
 13
 14 int cmp(int *r , int a , int b , int l)
 15 {
 16     return r[a]==r[b] && r[a+l]==r[b+l];
 17 }
 18
 19 void getSa(int *r , int *sa , int n , int m)
 20 {
 21     int *x = wa , *y = wb , *t;
 22     for(int i=0 ; i<m ; i++) wsf[i]=0;
 23     for(int i=0 ; i<n ; i++) wsf[x[i]=r[i]]++;
 24     for(int i=1 ; i<m ; i++) wsf[i]+=wsf[i-1];
 25     for(int i=n-1 ; i>=0 ; i--) sa[--wsf[x[i]]] = i;
 26
 27     int i,j,p=1;
 28     for(j=1 ; p<n ; j*=2 , m=p)
 29     {
 30         for(p=0 , i=n-j ; i<n ; i++) y[p++] = i;
 31         for(i=0 ; i<n ; i++) if(sa[i]>=j) y[p++] = sa[i]-j;
 32
 33         for(i=0 ; i<n ; i++) wv[i]=x[y[i]];
 34         for(i=0 ; i<m ; i++) wsf[i]=0;
 35         for(i=0 ; i<n ; i++) wsf[wv[i]]++;
 36         for(i=1 ; i<m ; i++) wsf[i]+=wsf[i-1];
 37         for(i=n-1 ; i>=0 ; i--) sa[--wsf[wv[i]]] = y[i];
 38
 39         for(t=x , x=y , y=t , x[sa[0]]=0 , p=1 , i=1; i<n ; i++)
 40             x[sa[i]] = cmp(y , sa[i-1] , sa[i] , j)?p-1:p++;
 41     }
 42     return ;
 43 }
 44
 45 void callHeight(int *r , int *sa , int n)
 46 {
 47     for(int i=0 ; i<=n ; i++) rank[sa[i]]=i;
 48     int i , j , k=0;
 49     for(i=0 ; i<n ; height[rank[i++]]=k)
 50         for(j=sa[rank[i]-1] , k?k--:0 ; r[i+k]==r[j+k] ; k++) ;
 51     return;
 52 }
 53
 54 ll solve(int len1 , int len2)
 55 {
 56     ll ans = 0;
 57     //B串中的子串不断匹配rank比其高的A子串
 58     int top = 0;
 59     ll tot =0 , cnt = 0;
 60     for(int i=1 ; i<=len1+len2+1 ; i++){
 61         if(height[i]<k){
 62             top = tot = 0;
 63             continue;
 64         }
 65         cnt = 0;
 66         if(sa[i-1]<len1){
 67             cnt ++;
 68             tot += height[i]-k+1;
 69         }
 70         while(top&&height[i]<=q[top][0]){
 71             tot -= q[top][1]*(q[top][0]-height[i]);
 72             cnt += q[top][1];
 73             top--;
 74         }
 75         q[++top][0] = height[i];
 76         q[top][1] = cnt;
 77         if(sa[i]>len1) ans+=tot;
 78     }
 79     //A串中的子串不断匹配rank比其高的B子串
 80     tot = top = 0;
 81     for(int i=1 ; i<=len1+len2+1 ; i++){
 82         if(height[i]<k){
 83             top = tot = 0;
 84             continue;
 85         }
 86         cnt = 0;
 87         if(sa[i-1]>len1){
 88             cnt ++;
 89             tot += height[i]-k+1;
 90         }
 91         while(top&&height[i]<=q[top][0]){
 92             tot -= q[top][1]*(q[top][0]-height[i]);
 93             cnt += q[top][1];
 94             top--;
 95         }
 96         q[++top][0] = height[i];
 97         q[top][1] = cnt;
 98         if(sa[i]<len1) ans+=tot;
 99     }
100     return ans;
101 }
102
103 int main()
104 {
105    // freopen("a.in" , "r" , stdin);
106
107     while(scanf("%d" , &k) , k)
108     {
109         scanf("%s%s" , str1 , str2);
110         int len1 = strlen(str1) , len2 = strlen(str2);
111         for(int i=0 ; i<len1 ; i++) a[i] = (int)str1[i];
112         a[len1] = 259;
113         for(int i=0 ; i<len2 ; i++) a[i+len1+1] = (int)str2[i];
114         a[len1+len2+1] = 0;
115
116         getSa(a , sa , len1+len2+2 , 260);
117         callHeight(a , sa , len1+len2+1);
118
119       //  for(int i=0 ; i<len1+len2+2 ; i++) cout<<"rank i: "<<i<<" "<<rank[i]<<endl;
120       //  for(int i=1 ; i<len1+len2+2 ; i++) cout<<"xixi: "<<height[i]<<endl;
121         ll ans = solve(len1 , len2);
122         printf("%I64d\n" , ans);
123     }
124     return 0;
125 }
时间: 2024-10-28 15:09:06

POJ 3415 后缀数组+单调栈的相关文章

POJ 3415:后缀数组+单调栈优化

题意很简单,求两个字符串长度大于等于K的子串个数 一开始还是只会暴力..发现n^2根本没法做...看了题解理解了半天才弄出来,太弱了... 思路:把两个字符串连接后做一次后缀数组,求出height 暴力的想法自然是枚举第一个子串的起始位置i和第二个子串的起始位置j,肯定会T的 看了题解才知道有单调栈这种有优化方法.. 将后缀分为A组(起始点为第一个字符串).B组 设符合要求的lcp长度为a,则其对答案的贡献为a-k+1(长度为k~a的都是符合要求的) 一开始这里我也是有疑问的,比如说k=1,aa

HUID 5558 Alice&#39;s Classified Message 后缀数组+单调栈+二分

http://acm.hdu.edu.cn/showproblem.php?pid=5558 对于每个后缀suffix(i),想要在前面i - 1个suffix中找到一个pos,使得LCP最大.这样做O(n^2) 考虑到对于每一个suffix(i),最长的LCP肯定在和他排名相近的地方取得. 按排名大小顺序枚举位置,按位置维护一个递增的单调栈,对于每一个进栈的元素,要算一算栈内元素和他的LCP最大是多少. 如果不需要输出最小的下标,最大的直接是LCP(suffix(st[top]),  suff

poj 3415 后缀数组分组+排序+并查集

Source Code Problem: 3415   User: wangyucheng Memory: 16492K   Time: 704MS Language: C++   Result: Accepted Source Code #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; #define N 510000

【bzoj3238】[Ahoi2013]差异 后缀数组+单调栈

题目描述 输入 一行,一个字符串S 输出 一行,一个整数,表示所求值 样例输入 cacao 样例输出 54 题解 后缀数组+单调栈,几乎同 bzoj3879 的后半部分. 我明显是做题做反了... 这里还是说一下这道题的做法. 先用后缀数组求出height. 然后由于有LCP(a,c)=min(LCP(a,b),LCP(b,c))(rank[a]<rank[b]<rank[c]),所以我们只需要知道排名相邻的两个后缀的LCP,而这就是height数组的定义. 转化为子问题:给出n个数,求所有子

[HAOI2016]找相同字符(后缀数组+单调栈)

[HAOI2016]找相同字符(后缀数组+单调栈) 题面 给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数.两个方案不同当且仅当这两个子串中有一个位置不同. 分析 我们把两个字符串接在一起,中间加一个分隔符.如\(\text{AABB}\)和\(\text{BBAA}\)变成\(\text{AABB|BBAA}\).我们考虑两个相同字串,如\(\text{BB}\),它在新串中对应了两个后缀\(BB|BBAA\)和\(\text{BBAA}\)的LCP. 容易发现,LC

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

[题目链接] http://poj.org/problem?id=3415 [题目大意] 求出两个字符串长度大于k的公共子串的数目. [题解] 首先,很容易想到O(n2)的算法,将A串和B串加拼接符相连, 做一遍后缀数组,把分别属于A和B的所有后缀匹配,LCP-k+1就是对答案的贡献, 但是在这个基础上该如何优化呢. 我们可以发现按照sa的顺序下来,每个后缀和前面的串的LCP就是区间LCP的最小值, 那么我们维护一个单调栈,将所有单调递减的LCP值合并, 保存数量和长度,对每个属于B串的后缀更新

POJ 3415 Common Substrings(长度不小于k 的公共子串的个数--后缀数组+单调栈优化)

题意:给定两个字符串A 和B,求长度不小于k 的公共子串的个数(可以相同). 样例1: A="xx",B="xx",k=1,长度不小于k 的公共子串的个数是5. 样例2: A ="aababaa",B ="abaabaa",k=2,长度不小于k 的公共子串的个数是22. 思路: 如果i后缀与j后缀的LCP长度为L, 在L不小于K的情况下, 它对答案的贡献为L - K + 1. 于是我们可以将两个串连起来, 中间加个奇葩的分隔符

POJ 3415 后缀数组

链接: http://poj.org/problem?id=3415 题意: 统计A和B长度不小于K的公共子串个数. 题解: 将A和B拼接后,利用单调栈累计分属两者的后缀对应的LCP-K+1即为答案 代码: 31 int n, k; 32 int Rank[MAXN], tmp[MAXN]; 33 int sa[MAXN], lcp[MAXN]; 34 35 bool compare_sa(int i, int j) { 36 if (Rank[i] != Rank[j]) return Ran

bzoj 3238 [Ahoi2013]差异 后缀数组 + 单调栈

题目链接 Description 一个长度为\(n\)的字符串\(S\),令\(T_i\)表示它从第\(i\)个字符开始的后缀.求\[\sum_{1\leq i\leq j\leq n}len(T_i)+len(T_j)-2*lcp(T_i,T_j)\]其中,\(len(a)\)表示字符串\(a\)的长度,\(lcp(a,b)\)表示字符串\(a\)和字符串\(b\)的最长公共前缀. \(2\leq n\leq 500000\) 思路 \(O(n^2)\)枚举显然是不可行的,应从 贡献 的角度取