POJ 3294 Life Forms(后缀数组)

这篇博客介绍了POJ 3294题目,即寻找所有出现在超过一半字符串中的最长子串。作者分享了解题思路,通过二分法判断子串可行性,并记录起点。由于初期错误导致调试方向偏差,最终解决问题并提供了代码实现。解决方案包括将高度按大于等于k分组,检查每组是否在至少一半字符串中出现。
摘要由CSDN通过智能技术生成

题目链接:http://poj.org/problem?id=3294

解题思路:

求出所有 最长的 出现在超过一半所给字符串的 子串。


这题是在错自闭了可以去POJ discuss里面,有本题数据的。

反正我是疯狂OLE,RE了一晚上。

最后发现OLE是代码写错导致输出的串的数量不对(其实也就是WA),所以导致DEBUG的方向就出错了。


做法是二分答案,判断是否可行,可行就记录当前所有可行的起点。

判断方法就是把height按照>=k分成一组组,每一组内部看是否属于至少有超过一半的字符串有这个部分。

二分最后得到一个答案以及一堆起点,由于需要按照字典序输出。

所以扔进set排个序再扔出来。


代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<map>
#include<set>

using namespace std;

#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define pb push_back
#define fi first
#define se second
#define debug(x) printf("----Line %s----\n",#x)
#define pt(x,y) printf("%s = %d\n",#x,y)
#define INF 0x3f3f3f3f

const int N = 1020*100;

char ss[1020];
int s[N];
int wa[N],wb[N],rnk[N],height[N],sa[N],cnt[N];
int dd[110],ans[N],tot;
set<string>sss;

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

    for (j=1,p=1;p<n;j<<=1,m=p){
        for (i=n-j,p=0;i<n;i++) y[p++] = i;
        for0(i,0,n) if (sa[i]>=j) y[p++] = sa[i]-j;

        for0(i,0,m) cnt[i] = 0;
        for0(i,0,n) cnt[x[y[i]]]++;
        for0(i,1,m) cnt[i] += cnt[i-1];
        for (int 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;
    for0(i,0,n) rnk[sa[i]] = i;
    for0(i,0,n){
        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;
    }
}

bool vis[110];

bool check(int kk,int len,int need,int n)///框kk,判断是否框内够need个不同的,把当前答案记下来
{
    int cnt = 0;
    bool flag = false,first = true;
    memset(vis,false,sizeof vis);
    for (int i=0;i<len;i++){///找一堆都>=k的 height
        if (height[i]>=kk){
            int j;
            for (j=i;(j+1)<len && height[j+1]>=kk;j++);
            for (int k=i-1;k<=j;k++){///判断当前在一个框内的后缀属于几个不同的
                int pos = lower_bound(dd,dd+n+1,sa[k])-dd;///复杂度log(100),可以就当O(1)
                if (!vis[pos]) cnt++;
                vis[pos] = true;
                if (cnt>=need) {flag = true;break;}
            }
            if (cnt>=need && flag){
                if (first) tot = 0,first = false;
                ans[tot++] = sa[i];
            }
            cnt = 0;
            memset(vis,false,sizeof vis);
            i = j;
        }
    }
    return flag;
}

int main()
{
    //freopen("C:/Users/DELL/Desktop/input.txt", "r", stdin);
    //freopen("C:/Users/DELL/Desktop/output.txt", "w", stdout);
    int n;
    bool mother_fuck_ole = true,mfo2 = false;
    while (~scanf("%d",&n),n){
        if (mother_fuck_ole) mother_fuck_ole = false;///纯属自我发泄,最后一组有效数据后面有无空行都无所谓
        else puts("");

        dd[1] = -1;
        tot = 0;
        int dzd = 200;
        for (int i=1;i<=n;i++){
            scanf("%s",ss);
            int len = strlen(ss);
            dd[i+1] = dd[i] + len + 1;
            if (i!=n) s[dd[i+1]] = dzd++;
            int idx = 0;
            for (int j = dd[i]+1;j<dd[i+1];j++) s[j] = ss[idx++];
        }

        buildsa(dd[n+1]+1,400);
        getheight(dd[n+1]+1);

        int l = 0,r = 1001;
        while (l<=r){

            int k = l+r>>1;
            if (check(k,dd[n+1]+1,n/2+1,n)) l = k+1;
            else r = k-1;
        }

        if (r==0) {printf("?\n");}
        else {
            sss.clear();
            for (int i = 0;i<tot;i++){
                string ssss="";
                for (int j = ans[i];j<ans[i]+r;j++) ssss.push_back(s[j]);
                sss.insert(ssss);
            }
            //for (auto now:sss) cout << now << "\n";
            set<string>::iterator it,it2;
            for (it=sss.begin();it!=sss.end();it++) {cout << *it << "\n";}
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值