poj Common Substrings(后缀数组&单调队列)


Common Substrings

Time Limit: 5000MS   Memory Limit: 65536K
Total Submissions: 7082   Accepted: 2355

Description

A substring of a string T is defined as:

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

Given two strings AB and one integer K, we define S, a set of triples (ijk):

S = {(ijk) | kKA(ik)=B(jk)}.

You are to give the value of |S| for specific AB 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

Source

POJ Monthly--2007.10.06, wintokk

题意:

给你两个长度不超过10^5的字符串A,B。和一个数字K。K<=MIN(|A|,|B|)。现在问你A,B的公共子串中长度不小于K的有多少对。位置可以重叠。

思路:

既然涉及到公共子串容易想到的是利用后缀数组的heght数组。对于长度不小于K的公共子串。很容易到想把A,B串连接起来中间用一个特殊字符隔开按height数组分组。heght不小于k的连续height分一组。这样就可以保证同组内的公共前缀(子串)长度不小于K了。然后就是统计长度不小于K的公共子串的对数了。比较好想的做法是对组内每一个B的后缀。计算它与组内的每一个A的后缀能产生多少长度不小于K的公共子串。假设B额后缀b与A的后缀a的最长公共前缀的长度为len。那么他们能产生长度不小于K的公共子串的个数为len-K+1.为什么可以这样统计呢。因为不同的后缀在字符串的起点肯定不一样。所以统计子串只要统计后缀的前缀就行了。但是这样统计的话时间复杂度为O(n^2)。明显过不了。只能换一种思维统计。对于每一个B的后缀我们知道它与前面所有A的后缀能产生多少答案。然后对于A的每一个后缀它与前面B的后缀能产生多少答案。这样我们只要扫描两次就能得到最终答案了。现在关键就是怎样快速做到对于每一个B的后缀我们知道它与前面所有A的后缀能产生多少答案了。可以知道影响两个后缀的最长公共前缀的是中间最小的heght。所以我们想到用单挑栈来维护这个信息。这样我们就把heght一个一个往栈里加。这个栈的第i个元素维护目前有num[i]个A的后缀到当前位置的he[i]最小值是q[i]。这样我们每遇到一个A的后缀那么它对后面B的贡献为height[i]-K+1.一直到它到待计算B中间有height值小于它。所以我们记前面A对后面B的贡献为base。假设有新的height加进栈会使一num[a]个q[a],num[b]个q[b]。。。。的A的后缀到后面B的heght值变成height[i]。那么base=base-(q[a]-heght[i])*num[a]-(q[b]-heght[i])*num[b]。然后遇到B的后缀加上base就行了。统计A的前面B的后缀同理。这个问题就圆满解决了。

详细见代码:

#include<algorithm>
#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=200020;
typedef long long ll;
int sa[maxn],T1[maxn],T2[maxn],he[maxn],rk[maxn],ct[maxn],q[maxn],num[maxn];
int n,m;
char txt[maxn];
ll ans;
void getsa(char *st)
{
    int i,k,p,*x=T1,*y=T2;
    for(i=0; i<m; i++) ct[i]=0;
    for(i=0; i<n; i++) ct[x[i]=st[i]]++;
    for(i=1; i<m; i++) ct[i]+=ct[i-1];
    for(i=n-1; i>=0; i--)
        sa[--ct[x[i]]]=i;
    for(k=1,p=1; p<n; k<<=1,m=p)
    {
        for(p=0,i=n-k; i<n; i++) y[p++]=i;
        for(i=0; i<n; i++) if(sa[i]>=k) y[p++]=sa[i]-k;
        for(i=0; i<m; i++) ct[i]=0;
        for(i=0; i<n; i++) ct[x[y[i]]]++;
        for(i=1; i<m; i++) ct[i]+=ct[i-1];
        for(i=n-1; i>=0; i--) sa[--ct[x[y[i]]]]=y[i];
        for(swap(x,y),p=1,x[sa[0]]=0,i=1; i<n; i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
    }
}
void gethe(char *st)
{
    int i,j,k=0;
    for(i=0;i<n;i++) rk[sa[i]]=i;
    for(i=0;i<n-1;i++)
    {
        if(k) k--;
        j=sa[rk[i]-1];
        while(st[i+k]==st[j+k]) k++;
        he[rk[i]]=k;
    }
}
int main()
{
    int mid,i,k,tp,cnt;
    ll base;

    while(scanf("%d",&k),k)
    {
        scanf("%s",txt);
        mid=n=strlen(txt);
        txt[n]='$';
        scanf("%s",txt+n+1);
        n=strlen(txt)+1;
        m=150,ans=tp=base=0;
        getsa(txt);
        gethe(txt);
        for(i=1;i<n;i++)
        {
            if(he[i]<k)
                tp=0,base=0;//tp为栈顶。base为前面对后面的贡献
            else
            {
                cnt=0;//统计有多少A的后缀是以he[i]为最小值的。
                if(sa[i-1]<mid)//注意这里必须为sa[i-1]。因为he[i]为i和i-1的最长公共前缀。所以只能确定sa[i-1]与后面的关系
                    cnt++,base+=he[i]-k+1;
                while(tp&&he[i]<q[tp-1])
                {
                    base-=(q[tp-1]-he[i])*num[tp-1];//最小值改变。对后面的b产生影响
                    cnt+=num[tp-1];
                    tp--;
                }
                q[tp]=he[i];
                num[tp++]=cnt;
                if(sa[i]>mid)
                    ans+=base;
            }
        }
        tp=0,base=0;
        for(i=1;i<n;i++)
        {
            if(he[i]<k)
                tp=0,base=0;
            else
            {
                cnt=0;
                if(sa[i-1]>mid)
                    cnt++,base+=he[i]-k+1;
                while(tp&&he[i]<q[tp-1])
                {
                    base-=(q[tp-1]-he[i])*num[tp-1];
                    cnt+=num[tp-1];
                    tp--;
                }
                q[tp]=he[i];
                num[tp++]=cnt;
                if(sa[i]<mid)
                    ans+=base;
            }
        }
        printf("%I64d\n",ans);
    }
    return 0;
}
时间: 2024-07-29 19:22:48

poj Common Substrings(后缀数组&单调队列)的相关文章

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

一般遇到多串问题,就用不同的符号把他们接起来,当成一个串来处理. 如A串是"aaaba",B串是"abaa". 把height数组按照不小于K分组,假设K = 2.从前向后扫描,对于每一组中的每个B,考虑前面A对其的贡献. 可以用栈来维护A的值.当要入栈的height值大于栈顶的值,统计得到的子串的数目 (+= height[i]-K+1). 如果小于,总和减去多加的部分,pop到小于height[i]为止,再入栈算贡献.

POJ3415:Common Substrings(后缀数组+单调栈)

Description A substring of a string T is defined as: T(i, k)=TiTi+1...Ti+k-1, 1≤i≤i+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) | k≥K, A(i, k)=B(j, k)}. You are to give the value of |S| f

POJ 1226 Substrings (后缀数组)

题目大意: 问的是m个字符串里,都出现过的子串.子串也可以出现在这个串的逆序串中. 思路分析: 居然wa在全5个 "a" 的数据上. 二分的时候下界不能为0.. 思路大致上是把原串和逆序串全部处理出来,放入str中,然后在每个串中间加一个没有出现过的. 此处注意输入不仅仅是字母. 然后跑一遍后缀数组. 然后用标记计数就好了. #include <iostream> #include <cstdio> #include <algorithm> #inc

POJ 3415 Common Substrings(后缀数组求重复字串)

题目大意:给你两个字符串,让你求出来两个字符串之间的重复子串长度大于k的有多少个. 解题思路: 先说论文上给的解释:基本思路是计算A的所有后缀和B的所有后缀之间的最长公共前缀的长度,把最长公共前缀长度不小于k的部分全部加起来.先将两个字符串连起来,中间用一个没有出现过的字符隔开.按height值分组后,接下来的工作便是快速的统计每组中后缀之间的最长公共前缀之和.扫描一遍,每遇到一个B的后缀就统计与前面的A的后缀能产生多少个长度不小于k的公共子串,这里A的后缀需要用一个单调的栈来高效的维护.然后对

POJ 3415 Common Substrings 后缀数组+并查集

后缀数组,看到网上很多题解都是单调栈,这里提供一个不是单调栈的做法, 首先将两个串 连接起来求height   求完之后按height值从大往小合并.  height值代表的是  sa[i]和sa[i-1] 的公共前缀长度,那么每次合并就是合并  i和i-1 那么在合并小的时候公共前缀更大的肯定已经都合并在一起,那么就可以直接统计了. #include<iostream> #include<cstdio> #include<algorithm> #include<

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

Description A substring of a string T is defined as: T( i, k)= TiTi+1... Ti+k-1, 1≤ i≤ i+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) | k≥ K, A( i, k)= B( j, k)}. You are to give the val

POJ 3415 Common Substrings ——后缀数组

[题目分析] 判断有多少个长度不小于k的相同子串的数目. N^2显然是可以做到的. 其实可以维护一个关于height的单调栈,统计一下贡献,就可以了. 其实还是挺难写的OTZ. [代码] #include <cstdio> #include <cstring> #include <cmath> #include <cstdlib> #include <map> #include <set> #include <queue>

HDU 3410 &amp;&amp; POJ 3776 Passing the Message 单调队列

题意: 给定n长的数组(下标从1-n)(n个人的身高,身高各不相同 问:对于第i个人,他能看到的左边最矮的人下标.(如果这个最矮的人被挡住了,则这个值为0) 还有右边最高的人下标,同理若被挡住了则这个值为0 输出n行,每个人左右2边的下标. 单调队列,对于 front - rear 的队列(注意出队都是在rear,入队也是在rear) 当加入元素x,若这队列是单调递增的,显然q.front() <= x , 反之若>x ,则把队首元素弹掉,这样就能保持单调性. 若弹掉了队首元素,在此题中就相当

POJ 2823 Sliding Window 【单调队列】

题目链接:http://poj.org/problem?id=2823 题目大意:给出一组数,一个固定大小的窗口在这个数组上滑动,要求出每次滑动该窗口内的最大值和最小值. 这就是典型的单调队列,单调队列的作用就在此.单调队列的队首为区间内的最值,但是整个队列不用保持单调. 用两个队列分别处理最大值和最小值,在此说明一下最大值: 往队列中添加值num时,从队尾开始扫,直到遇到一个小于num的d值,将num插入d的后一位.之后的元素全部无效化(不管后面的元素就行).查找最大值的时候,从队首开始找,如