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

题意很简单,求两个字符串长度大于等于K的子串个数

一开始还是只会暴力。。发现n^2根本没法做。。。看了题解理解了半天才弄出来,太弱了。。。

思路:把两个字符串连接后做一次后缀数组,求出height

暴力的想法自然是枚举第一个子串的起始位置i和第二个子串的起始位置j,肯定会T的

看了题解才知道有单调栈这种有优化方法。。

将后缀分为A组(起始点为第一个字符串)、B组

设符合要求的lcp长度为a,则其对答案的贡献为a-k+1(长度为k~a的都是符合要求的)

一开始这里我也是有疑问的,比如说k=1,aa和aab两个suffix,lcp=2,这样的话贡献为2-1+1=2

但是我们可以看到有这两个suffix符合要求的是有3个的(长度1的2个,2的1个),我们似乎只统计了长度1的和长度2的各一个

确实如此。但是我们不能小范围的看问题,应该要结合整个sa[]和height[]

这里没统计不意味着之后不统计,还会有一个a后缀和一个ab后缀,这里又贡献了1.这样就补齐了~

我们知道两个suffix的lcp是height中对应段height[_rank[i]+1]~height[_rank[j]]的最小值,应用这个性质我们可以来维护单调递增的栈

为什么要这样呢?可以理解为栈里是可能被用到的候选序列,如果当前扫描到的height小于栈顶(候选最大值),则根据上面的性质,

可以得出大于height的值是无法做出贡献了(或者说贡献变小了),那累加器的值要更新

同时我们这里用到了一个优化,为了防止栈内元素过多,我们把同一个height值,捆绑一个个数num,这样可以提升统计效率

我们对A、B组各做一次,加起来就是答案

一开始我在这里没有完全理解,总感觉会重复统计。其实是没有的。为什么呢?

那统计B组的时候为例。

对于B组后缀j,我统计答案都是在sa[j]之前找。

比如说找到A组中的ii,jj,kk三个后缀是符合的,那必定有sa[ii]、sa[jj]、sa[kk]都小于sa[j]

所以在统计A组时,sa[ii]也是在sa[ii]之前找,不可能找到sa[j]

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#define LL long long
#define mems(a,b) memset(a,b,sizeof(a))
#define ls pos<<1
#define rs pos<<1|1
#define max(a,b) (a)>(b)?(a):(b)
using namespace std;

const int MAXN = 200500;
const int INF = 0x3f3f3f3f;

char a[MAXN],b[MAXN];
int sa[MAXN],_rank[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],Ws[MAXN];
int cmp(int *r,int a,int b,int l)
{return r[a]==r[b]&&r[a+l]==r[b+l];}
void get_sa(char *r,int *sa,int n,int m){
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++) Ws[i]=0;
    for(i=0;i<n;i++) Ws[x[i]=r[i]]++;
    for(i=1;i<m;i++) Ws[i]+=Ws[i-1];
    for(i=n-1;i>=0;i--) sa[--Ws[x[i]]]=i;
    for(j=1,p=1;p<n;j*=2,m=p){
        for(p=0,i=n-j;i<n;i++) y[p++]=i;
        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<m;i++) Ws[i]=0;
        for(i=0;i<n;i++) Ws[wv[i]]++;
        for(i=1;i<m;i++) Ws[i]+=Ws[i-1];
        for(i=n-1;i>=0;i--) sa[--Ws[wv[i]]]=y[i];
        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
    return;
}

int height[MAXN];
void get_height(char *r,int *sa,int n){
    int i,j,k=0;
    for(i=1;i<=n;i++) _rank[sa[i]]=i;
    for(i=0;i<n;height[_rank[i++]]=k)
    for(k?k--:0,j=sa[_rank[i]-1];r[i+k]==r[j+k];k++);
    return;
}

int k;
int main(){
    //freopen("in.txt","r",stdin);
    while(scanf("%d",&k)&&k){
        scanf("%s%s",a,b);
        int la=strlen(a);
        int lb=strlen(b);
        a[la]=‘$‘;
        for(int i=0;i<lb;i++) a[i+la+1]=b[i];
        int n=la+lb;    //最后一个元素的下标
        a[++n]=‘\0‘;
        get_sa(a,sa,n+1,300);
        get_height(a,sa,n);
        LL ans=0,cnt=0;         //cnt为累加器
        int top=0;
        pair<int,int> s[MAXN];
        for(int i=1;i<=n;i++){
            if(height[i]<k) top=0,cnt=0;
            else{
                int num=0;      //统计同一height值的个数
                if(sa[i-1]<la) num++,cnt+=height[i]-k+1;
                while(top&&height[i]<=s[top].first){
                    cnt-=s[top].second*(s[top].first-height[i]);    //如果栈顶元素的height大于等于当前height,则贡献会变小s[top].first-height[i],更新累加器
                    num+=s[top--].second;                           //更新个数
                }
                s[++top]=make_pair(height[i],num);
                if(sa[i]>la) ans+=cnt;
            }
        }
        top=0;
        cnt=0;
        for(int i=1;i<=n;i++){
            if(height[i]<k) top=0,cnt=0;
            else{
                int num=0;
                if(sa[i-1]>la) num++,cnt+=height[i]-k+1;
                while(top&&height[i]<=s[top].first){
                    cnt-=s[top].second*(s[top].first-height[i]);
                    num+=s[top--].second;
                }
                s[++top]=make_pair(height[i],num);
                if(sa[i]<la) ans+=cnt;
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

时间: 2024-08-24 10:37:01

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

POJ 3415 后缀数组+单调栈

题目大意: 给定A,B两种字符串,问他们当中的长度大于k的公共子串的个数有多少个 这道题目本身理解不难,将两个字符串合并后求出它的后缀数组 然后利用后缀数组求解答案 这里一开始看题解说要用栈的思想,觉得很麻烦就不做了,后来在比赛中又遇到就后悔了,到今天看了很久才算看懂 首先建一个栈,从栈底到栈顶都保证是单调递增的 我们用一个tot记录当前栈中所有项和一个刚进入的子串匹配所能得到的总的子串的数目(当然前提是,当前进入的子串height值比栈顶还大,那么和栈中任意一个子串匹配都保持当前栈中记录的那时

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. 于是我们可以将两个串连起来, 中间加个奇葩的分隔符

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

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

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个数,求所有子

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 后缀数组

链接: 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)\)枚举显然是不可行的,应从 贡献 的角度取