POJ - 3294:Life Forms (后缀数组之求至少k个串中出现的子串)

 

题目大意:

给你n个串,求在至少n/2(大于n/2)个串出现的最长子串,按字典序从小到大输出。

 

解题思路:

日常二分,二分长度,然后对其存在进行判断即可。

这里我感觉用set处理起来比较简单,对于height[i]大于等于k的值,直接把 pos[sa[i]] 和 pos[sa[i-1]] 加入到set中。最后判断set中的元素个数是否大于n/2,如果大于 就把 sa[i] 储存即可。。。

刚开始犯了好几个智障的问题,

第一,第一次判断每个位置属于哪个字符串 即 pos数组 我是用一个数组记录每个串的结束位置然后二分的,其实直接在构造串的过程中把pos数组构造了即可。

第二,我当时判断set中元素个数合法以后是遍历height数组 把所有 那一类的 sa[i] 加进去的,其实明显不用,因为同一类他们表示的是同一个子串,是要被去重去掉的= = 。所以只加一个 sa[i]即可。

第三,为了字典序最小,开始写了个string 排了序,还顺带去重了一下,也是明显不需要,因为 sa数组本来就是按照字典序排的,你按照sa数组跑出来的自然是最小字典序。。。

除去这些还算是一道比较正常的题目吧。

 

Ac代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <algorithm>
#include <set>
#include <functional>
#define next ne
#define rank ra
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
const int INF=1e9+7;
char s[maxn];
int V[maxn],b[maxn];
vector<int> ans;
set<int> st;
int n,m,cnt,sa[maxn],rank[maxn],height[maxn];
int t1[maxn],t2[maxn],r[maxn],c[maxn];
bool cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b] && r[a+l]==r[b+l];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m)
{
    n++;
    int i,j,p,*x=t1,*y=t2;
    for(int i=0;i<m;i++) c[i]=0;
    for(int i=0;i<n;i++) c[x[i]=str[i]]++;
    for(int i=1;i<m;i++) c[i]+=c[i-1];
    for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
    for(int j=1;j<=n;j<<=1)
    {
        p=0;
        for(int i=n-j;i<n;i++) y[p++]=i;
        for(int i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
        for(int i=0;i<m;i++) c[i]=0;
        for(int i=0;i<n;i++) c[x[y[i]]]++;
        for(int i=1;i<m;i++) c[i]+=c[i-1];
        for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1,x[sa[0]]=0;
        for(int i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        if(p>=n) break;
        m=p;
    }
    int k=0;
    n--;
    for(int i=0;i<=n;i++) rank[sa[i]]=i;
    for(int i=0;i<n;i++)
    {
        if(k) k--;
        j=sa[rank[i]-1];
        while(str[i+k]==str[j+k]) k++;
        height[rank[i]]=k;
    }
}
bool check(int lt,int flag)
{
    st.clear();ans.clear(); //注意这里set要清空
    for(int i=2;i<=n;i++)
    {
        if(height[i]>=lt)
        {
            st.insert(b[sa[i-1]]);  //直接加入set即可
            st.insert(b[sa[i]]);                //满足条件就return 1
            if(st.size()>m&&flag==0) return 1;  //flag是为了方便之后的运算
        }
        else    //如果flag为1 就将sa[i]加入答案
        {
            if(st.size()>m&&flag) ans.push_back(sa[i-1]);
            st.clear();
        }
    }
    if(st.size()>m&&flag) ans.push_back(sa[n]); //最后记得也要判断一下
    return 0;
}
int main()
{
    while(scanf("%d",&m)!=EOF)
    {
        if(m==0) break;
        st.clear();
        int len=0,num=30;cnt=0;
        for(int i=1;i<=m;i++)   //处理出 r 数组
        {                       //同时处理出 pos 数组 即 b 数组
            scanf(" %s",s);
            int ls=strlen(s);
            for(int j=0;j<ls;j++) b[len]=i,r[len++]=s[j]-'a'+1;
            r[len++]=num++;
        }
        m=m/2;  //除2 方便之后的运算
        r[len-1]=0;n=len;
        da(r,sa,rank,height,n,num);
        int L=0,R=INF,res=0;
        while(L<=R) //二分长度
        {
            int mid=(L+R)>>1;
            if(check(mid,0)) res=mid,L=mid+1;
            else R=mid-1;
        }
        if(res==0) printf("?\n");
        else 
        {
            check(res,1);   //这里重新跑一遍 check
            for(int i=0;i<ans.size();i++)   //输出所有的子串
            {
                for(int j=ans[i];j<ans[i]+res;j++)
                    printf("%c",r[j]-1+'a');
                printf("\n"); 
            }
        }
        printf("\n");
    }
    //system("pause");
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值