后缀数组,LCP(生命的形式,UVA 11107)

博客讲述了在解决UVA 11107问题时,利用后缀数组和最长公共前缀(LCP)算法的过程。博主分享了错误和正确解法的思考,强调了处理字符范围、数组大小以及输入循环条件的重要性。文章还介绍了后缀数组和LCP的概念,以及它们在查找和统计子串中的应用。

看了别人的代码才过的。

http://blog.youkuaiyun.com/scut_pein/article/details/19786707

果然抄别人的代码就会一点都不理解,很多细节都没注意到的。自己搞了好多发RE,TLE,WA才过。


就是char最大为127,最小为-128,而字符种类数必然大于这个值(主要是有100个各不相同的分隔符),所以需要用int形数组来保存合并后的串。

vis是用来标记串编号的,所以只要开大于100的空间就够了。。

合并后的串不但有100个长度为1000的串还有100个分隔符,所以空间至少要开100100。

是while(~scanf("%d",&N)&&N)或者while(scanf("%d",&N)==1&&N)而不是while(~scanf("%d",&N))。。。


自己没有想到解决办法,正确解法是将所有串连成一个大长串,并在串与串之间加一个从未出现过的分隔符将他们隔开,别忘了最后补个结束符。然后对这个串跑一遍后缀数组以及最长公共前缀,从而得到height数组。最后二分公共前缀的长度,然后去检查是否有超过一半的串都拥有这个长度的公共前缀。检查的方法就是寻找连续的height数组的区间,使得区间内的height值都大于公共前缀的长度,而且区间内包含的串的种类数(来自同一个串的后缀算一种)大于串的总个数的一半。再讲具体一点就是遍历一遍,维护区间长度以及区间内串的种类数,然后判断下有没有找到就好了。这类似最小值最大的问题,所以理应想到二分。为何可以这么检查呢?因为后缀数组其实就是将一个字符串的所有后缀排序,然后sa[i]就是后缀i的排名。而height[i]是第i名的后缀与第i-1名的后缀的最长公共前缀。第i名与第i-1名的后缀不一定是后缀j与后缀j-1,但是由于排名是相邻的,所以他们的前缀会非常相似。从而易得出连续的height区间也就是连续的排名区间内的串都具有相似的前缀,他们的最长公共前缀的长度就是height区间中的最小值。如果一个height[i]>=len的height区间被一个height[i]<len的单点隔断了,那么此区间就会被分为两个height区间,虽然两个区间内的最长公共前缀依旧>=len但是总区间的最长公共前缀却只能小于len了,就算两个区间的LCP长度相等,他们也是不同的LCP了,这就是为什么区间一定要连续的原因。至于为什么要统计串的种类就很好理解啦,因为自己不应该和自己匹配呀。那为什么要在串与串之间加一个从未出现过的分隔符将他们隔开呢?首先,我们的算法是将多个串连成一个大长串,然后让这个大长串自己和自己匹配,那么我们不希望出现跨串匹配的现象,因为这不符合题目要求,所以要用分隔符将他们分割开来,以防计算机不知道他们是不同的串。但是分隔符也是一个字符,万一分隔符们相互匹配,甚至和正常字符匹配了该怎么办呢?所以我们只好让他们从未出现过,那么他们就不会和任何其他的字符相匹配,从而达到隔离串的作用。


下面讲讲自己对后缀数组以及LCP算法的理解。

后缀数组就是对所有后缀进行排序,然后达到快速查找某个串是否在文本中出现的目的。而LCP算法是找一个串后缀与后缀之间的LCP。从而达到快速统计某个串中重复出现的子串及其长度的目的,稍加变换便能应用到统计多个串中重复出现的子串以及长度的问题中。


代码

#include<bits/stdc++.h>
    using namespace std;
    const int maxn = 101000;

    int s[maxn];
    char ss[maxn];
    int sa[maxn],t1[maxn],t2[maxn],c[maxn];
    int l;

    void get_sa(int m)
    {
        int *x=t1,*y=t2;
        for(int i=0;i<l;i++)
            x[sa[i]=i]=s[i];
        for(int i=0;i<=l;i=(i?i<<1:i+1))
        {
            int p=0;
            for(int j=l-i;j<l;j++) y[p++]=j;
            for(int j=0;j<l;j++) if(sa[j]>=i) y[p++]=sa[j]-i;
            for(int j=0;j<m;j++) c[j]=0;
            for(int j=l-1;j>=0;j--) c[x[y[j]]]++;
            for(int j=1;j<m;j++) c[j]+=c[j-1];
            for(int j=l-1;j>=0;j--) sa[--c[x[y[j]]]]=y[j];
            swap(x,y);
            p=1,x[sa[0]]=0;
            for(int j=1;j<l;j++)
                x[sa[j]]=y[sa[j-1]]==y[sa[j]]&&(sa[j-1]+i>=l?-1:y[sa[j-1]+i])==(sa[j]+i>=l?-1:y[sa[j]+i])?p-1:p++;
            if(p>=l) return;
            m=p;
        }
    }

    int ran[maxn],height[maxn];

    void get_hight()
    {
        for(int i=0;i<l;i++) ran[sa[i]]=i;
        int k=0;
        height[0]=0;
        for(int i=0;i<l;i++)
        {
            if(!ran[i]) continue;
            if(k) k--;
            int j=sa[ran[i]-1];
            while(s[i+k]==s[j+k]) k++;
            height[ran[i]]=k;
        }
    }

    int N;
    int belong[maxn];
    int vis[120];

    bool ok(int len)
    {
        int cnt=0;
        memset(vis,0,sizeof(vis));
        int num=N/2;
        vis[0]=0;
        if(!vis[belong[sa[0]]])
        {
            vis[belong[sa[0]]]=1;
            cnt++;
        }
        for(int i=1;i<l;i++)
        {
            if(height[i]>=len)
            {
                if(!vis[belong[sa[i]]])
                {
                    cnt++;
                    vis[belong[sa[i]]]=1;
                }
            }
            else
            {
                cnt=0;
                memset(vis,0,sizeof(vis));
                vis[0]=1;
                if(!vis[belong[sa[i]]])
                {
                    cnt++;
                    vis[belong[sa[i]]]=1;
                }
            }
            if(cnt>num) return 1;
        }
        return 0;
    }

    void print(int len)
    {
        int cnt=0;
        memset(vis,0,sizeof(vis));
        int num=N/2;
        vis[0]=0;
        if(!vis[belong[sa[0]]])
        {
            vis[belong[sa[0]]]=1;
            cnt++;
        }
        for(int i=1;i<l;i++)
        {
            if(height[i]>=len)
            {
                if(!vis[belong[sa[i]]])
                {
                    cnt++;
                    vis[belong[sa[i]]]=1;
                }
            }
            else
            {
                if(cnt>num)
                {
                    for(int j=0;j<len;j++)
                        printf("%c",s[sa[i-1]+j]-18);
                    puts("");
                }
                cnt=0;
                memset(vis,0,sizeof(vis));
                vis[0]=1;
                if(!vis[belong[sa[i]]])
                {
                    cnt++;
                    vis[belong[sa[i]]]=1;
                }
            }
        }
        if(cnt>num)
                {
                    for(int j=0;j<len;j++)
                        printf("%c",s[sa[l-2]+j]-18);
                    puts("");
                }
    }

    int fir=1;

    int main()
    {
        while(~scanf("%d",&N)&&N)
        {
            if(!fir) puts("");
            fir=0;
            int pos=0;
            int l=0,r=0;
            int hehe=0;
            for(int i=1;i<=N;i++)
            {
                scanf("%s",ss);
                int len=strlen(ss);
                r=max(r,len);
                for(int j=0;j<len;j++)
                {
                    s[pos+j]=ss[j]+18;
                    belong[pos+j]=i;
                }
                s[pos+len]=++hehe;
                belong[pos+len]=0;
                pos+=len+1;
            }
            ::l=pos;
            s[pos]=0;
            belong[pos]=0;
            get_sa(150);
            get_hight();
            r++;
            while(l<r)
            {
                int m=l+(r-l)/2;
                if(ok(m)) l=m+1;
                else r=m;
            }
            if(l==1) puts("?");
            else print(l-1);
        }
        return 0;
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值