HDU 1403 Longest Common Substring (后缀数组模板题)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1403

解题思路:数据100000,普通LCS超时。

用后缀数组 N*logN 求得LCS

首先一定要了解一下基数排序

参考的是罗穗骞大佬的论文,放个链接:https://wenku.baidu.com/view/5b886b1ea76e58fafab00374.html

对其中一些地方的理解:

为了形象,想象排好序之后所有后缀字符串装在桶里,比如sa[1]表示排好序后最小的,也就是第一个被倒出桶的

朋友你最好也画个桶,画一行字符串,不然容易晕。

一、求解sa数组需要了解的(论文中 void da函数部分)

①sa[i]  表示  桶中第i个倒出的后缀在实际字符串的位置

②rank[i] 表示 实际字符串中从位置i开始的后缀在桶中  排第几 / 分值(类似于整数排序当前位的数字大小)

③rank[sa[i]] = i ,

   sa[rank[i]] = i;

④rank[sa[i]+j] 表示桶中第i个倒出的后缀字符串  在实际字符串的位置后移j个位置  代表的后缀字符串  在桶中的排第几/分值

⑤sa[rank[i]+j] 表示i位置开头的后缀字符串在桶中的位置(排第几)后移j个 代表的后缀字符串 在实际字符串中的位置

二、我认为难点应该在这部分代码:(微改)

    for (j=1,p=1;p<n;j<<=1,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<m;i++) cnt[i] = 0;
        for (i=0;i<n;i++) cnt[x[y[i]]]++;
        for (i=1;i<m;i++) cnt[i] += cnt[i-1];
        for (i=n-1;i>=0;i--) sa[--cnt[x[y[i]]]] = y[i];

        swap(x,y); ///y储存上一轮rank值
        x[sa[0]] = 0;
        p = 1;
        for (i=1;i<n;i++)
            x[sa[i]] = ( y[sa[i]]==y[sa[i-1]] && y[sa[i]+j]==y[sa[i-1]+j] )? p-1:p++;
    }

这个for循环分为三个部分:

前两个for,根据上一轮的sa求出本轮排序第二关键字的sa(y数组)

上一轮的桶中如果sa[i] 倒出 某一位置开头的后缀字符串,现在应该倒出sa[i]-j开头的字符串,因为这是sa[i]-j开头的字符串的第二关键字!

中间四个for,根据第二关键字搞出的“新的一轮的桶”(y数组)来求解sa数组

x[y[i]] 表示当前倒出的字符串的rank值。

根据sa求解本轮rank数组(x数组),本来直接rank[sa[i]] = i。

不过,因为sa数组的值因为基数排序严格不同,但是实际上会有第一第二关键字完全相同的情况,so,需要再搞一搞。

三、height[]性质的粗略理解,以及h[i]>=h[i-1]-1的粗略证明

①为什么i开头的后缀字符串和j开头的后缀字符串的最长公共前缀(区别于LCS)等于 height[rank[i]+1] ~ height[rank[j]]

中的最小值?

求这个的过程就是桶中找到i后缀字符串的位置,j字符串的位置,然后中间夹着一堆,两两比较过去。

拿 aabaaaab来说,排好序之后:

aaaab

aaab

aab

aabaaaab

ab

abaaaab

b

baaaab

假设我们求的是aaab和abaaaab的后缀,aaab与aab比较后,当前height最小值为2,然后aab和aabaaaab比较,最小值=min(2,3)=2,还是2,直到传递到abaaaab,这个最小值其实就是,两两比较在传递下去时,aaab和当前在比较的字符串的

公共前缀的大小。

h[i] >= h[i-1]-1的证明

首先这个结论是为了减少构建height数组是字符比较次数。

继续看上面那个字符串,举个例子

aabaaaab和aab比较,height为3

接下去求解aabaaaab把“头”砍掉,求

abaaaab,我们知道桶中abaaaab上一位(也就是比较对象),至少前两位会是ab,也就是前(3-1)=2   个字符不用比

具体是不是ab不确定,可能会有abXXXX什么的,但,最不济就是ab

四、LCS的求法

具体方法是两个字符串拼接成一个。中间断点记做len

遍历height数组,只要height[i]所比较的两个后缀实际位置一个大于len,一个小于len,LCS=max(height[i])

这个真不会证明。知道的朋友可以评论告诉我一下。

先放着不管吧,以后刷多了题再回来想想。

五、代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#define debug(x) printf("----Line%s----\n",#x)

using namespace std;

const int N = 1e5+5;

int wa[N<<1],wb[N<<1],sa[N<<1],rnk[N<<1],cnt[N<<1],height[N<<1];
char s[N<<1];

void build(int n,int m)
{
    int *x=wa,*y=wb,p,i,j;
    for (i=0;i<m;i++) cnt[i] = 0;       
    for (i=0;i<n;i++) cnt[x[i]=s[i]]++;  
    for (i=1;i<m;i++) cnt[i] += cnt[i-1];
    for (i=n-1;i>=0;i--) sa[--cnt[x[i]]] = i;

    for (j=1,p=1;p<n;j<<=1,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<m;i++) cnt[i] = 0;
        for (i=0;i<n;i++) cnt[x[y[i]]]++;
        for (i=1;i<m;i++) cnt[i] += cnt[i-1];
        for (i=n-1;i>=0;i--) sa[--cnt[x[y[i]]]] = y[i];

        swap(x,y); 
        x[sa[0]] = 0;
        p = 1;
        for (i=1;i<n;i++)
            x[sa[i]] = ( y[sa[i]]==y[sa[i-1]] && y[sa[i]+j]==y[sa[i-1]+j] )? p-1:p++;
    }
}

void getheight(int n)
{
    int k=0;
    for (int i=0;i<n;i++) rnk[sa[i]] = i;
    for (int i=0;i<n;i++){
        if (rnk[i]==0){height[rnk[i]]=k=0;continue;}
        if (k) k--;
        int j = sa[rnk[i]-1];
        while (i+k<n && j+k<n && s[i+k]==s[j+k]) k++;
        height[rnk[i]] = k;
    }
}

int main()
{
    int n;
    while (~scanf("%s",s)){
        int len = strlen(s);
        scanf("%s",s+len+1);
        n = len + strlen(s+len+1) + 1;///+1很关键,虽然这题数据太水不+1也会AC
        build(n,200);//debug(1);
        getheight(n);//debug(2);
        int ans = 0;
        for (int i=1;i<n;i++)
            if ( (sa[i]<len && sa[i-1]>len) || (sa[i]>len && sa[i-1]<len) )
                ans = max(height[i],ans);
        //debug(3);
        printf("%d\n",ans);
    }
    return 0;
}

六、后记

后缀数组这个东西真的挺难的,与其每天花大把时间来想这个,不如每天抽个半小时想想,慢慢就想通了。

碰到难学的东西可能这么学效率会比较高。

然后我只能讲出我思考时遇到的困惑,如果有些地方没考虑,深感抱歉!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值