POJ 3294 Life Forms

老实说跑过来写博客是很惭愧的…毕竟没有靠自己的能力解决这道题…代码的关键部分也是借鉴了同学的,但是毕竟还是花了我3个小时的时间来调试,所以还是来稍微记录一下以免以后再忘记。

本周的学习内容是传说中的后缀数组,本来是试图弄明白后缀数组是怎么求出来的,在研究两位大神的论文无果之后,决定还是退而求其次研究研究后缀数组怎么用,模板什么的大不了下功夫先背下来等以后有了更深的体会以后再拿来研究研究。

后缀数组就是把某一个串的全部后缀拿出来比较然后对它们进行一次排序,排序的结果存在我们的sa数组里面,简单来说,sa[i]里面存的就是排名第i的后缀的第一个字符在字符串中的位置
与之对应的就是rank数组,rank[i]存储的是以第i个字符为开头的后缀在我们后缀数组中的排名。

另外一个比较重要的应用就是用后缀数组求height数组,height[i]存储的是后缀数组中排名第i-1和排名第i的后缀的最长公共前缀的长度,应用height数组我们可以来解决最长重复子序列的问题。

本题的本质就是求n个字符串中,重复次数超过k个的最长子串,所以我们首先要解决的问题就是如何求n个字符串的公共子串。

一个经典的解决方案是把这n个字符串全部链接起来,每两个字符串之间用一个字符串中未曾出现的字符进行间隔,而且所有的间隔符也是两两不同的,这样才能保证我们在求后缀数组的时候,不同的字符串之间不相互干扰。

求出height数组之后,如果在height的一片连续的区域height[sa[i]..sa[j]]内的值都相等,那么说明sa[i]…sa[j]代表的后缀具有相同的前缀,即具有重复的字串。
查找sa[i]…sa[j]原始分别在那些字符串里面即可得到该字符串的重复度,如果,在本题之中,字符串的重复度只要大于n/2就可以输出。

为了得到sa[i]代表的字符在哪个原始字符串中,设立了st_en[n][2]数组来存储每个字符串的起始位置和结束位置(这里应该是只记录每个字符串的结束位置就可以判断了,但是为了直观还是把起始地址和结束地址都记录下来)

为了找到我们所求的最长的满足要求的字符串,我们先确定一个字符串长度的最大值,即每个字符串长度的最大值,再确定一个字符串长度的最小值,即为0,之后的一个最直接的思路是对长度从大到小进行枚举,找到最大的符合要求的字符串长度,然后输出找到的字符串,但是这样效率肯定是比较低的,所以这里采用了二分的方法,从上界和下界的平均值开始找,折半缩小查找范围。

写的时候把sa数组变成局部变量,崩的心碎,调成全局变量就好了…也不是很懂为什么,下一步还是要多练习强化,寒假决定多刷几道题!

#include<cstdio>
#include<stdlib.h>
#include<string.h>
using namespace std;

#define maxn 100250
#define maxc 115
#define maxl 1105

int ws[maxn]={0}, wv[maxn]={0}, wa[maxn]={0}, wb[maxn]={0}; 
int rank[maxn]={0}, height[maxn]={0};
char data[maxc][maxl]={0};
int link_num[maxn]={0};
int len[maxn]={0};
int st_en[maxc][2]={0};
int sa[maxn]={0};

int cmp(int *r, int a, int b, int l){  
    return (r[a] == r[b]) && (r[a + l] == r[b + l]);  
}  

//计算后缀数组的模板 
void DA(int *r, int *sa, int n, int m)  {  
    int i, j, p, *x = wa, *y = wb, *t;  
    for (i = 0; i<m; i++) ws[i] = 0;  
    for (i = 0; i<n; i++) ws[x[i] = r[i]]++;  
    for (i = 1; i<m; i++) ws[i] += ws[i - 1];  
    for (i = n - 1; i >= 0; i--) sa[--ws[x[i]]] = i;   
    for (j = 1, p = 1; p<n; j *= 2, 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<n; i++) wv[i] = x[y[i]];  
        for (i = 0; i<m; i++) ws[i] = 0;  
        for (i = 0; i<n; i++) ws[wv[i]]++;  
        for (i = 1; i<m; i++) ws[i] += ws[i - 1];  
        for (i = n - 1; i >= 0; i--) sa[--ws[wv[i]]] = y[i];    
        for (t = x, x = y, y = t, p = 1, x[sa[0]] = 0, i = 1; i<n; i++)  
            x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++;  
    }  
}  

//计算height数组模板 
void calheight(int *r, int *sa, int n) {
    int i, j, k = 0;        
    for (i = 1; i <= n; i++) rank[sa[i]] = i;    

    for (i = 0; i<n; height[rank[i++]] = k)  
        for (k ? k-- : 0, j = sa[rank[i] - 1]; r[i + k] == r[j + k]; k++);   
}  

//通过某个数字在链接起来的数组里面的位置来判断这个数字是在初始的哪个字符串里面的 
int getID(int k,int n){
    for(int i=0;i<n;++i)
        if(k>=st_en[i][0]&&k<=st_en[i][1])
            return i;
    return -1;
}

//判断是否有超过一半的字符串含有我们找到的字符串 
bool check(int mid,int *h,int tcount,int n, bool output = false){
    int i=0,cnt = 0;
    while(true){
        bool visited[maxc]={0};
        while(i<tcount && height[i]<mid)
            ++i;
        if(i>=tcount)
            break;
        visited[getID(sa[i-1],n)] = true;

        cnt = 1;
        while(i<tcount&&height[i]>=mid){
            if(!visited[getID(sa[i],n)]){
                visited[getID(sa[i],n)] = true;
                cnt++;
            } 
            ++i;
        }
        if((cnt>n/2)&&output){
            for(int j=sa[i-1];j<sa[i-1]+mid;j++)
                printf("%c",link_num[j]+'a'-maxc);
            printf("\n");
        }
        else if(cnt>n/2)
            return true;

    }
    return false;
}

int main(){
    int n;

    while(true){
        memset(sa,0,sizeof(sa));
        memset(height,0,sizeof(height));
        memset(rank,0,sizeof(rank));

        int maxlen = 0;

        scanf("%d",&n);
        if(!n)
            break;
        for(int i=0;i<n;++i){
            scanf("%s",data[i]);
            len[i] = strlen(data[i]);
            if(len[i]>maxlen)
                maxlen = len[i];
        }

        if(n==1){
            printf("%s\n\n",data[0]);
            continue;
        }

        int tcount = 0;
        for(int i=0;i<n;++i){
            st_en[i][0] = tcount;
            for(int j=0;j<len[i];++j){
                link_num[tcount++] = (int)(data[i][j]-'a')+maxc;
            }
            st_en[i][1] = tcount;
            link_num[tcount++] = i;
        }

        DA(link_num,sa,tcount,200);

        calheight(link_num,sa,tcount-1);

        /*for(int i=0;i<tcount-1;++i){
            printf("%d ",height[i]);
        }
        printf("\n");*/

        int left = 0,right =maxlen+1;
        while(left<right){
            //printf("%d %d\n",left,right); 
            int mid = (left+right+1)/2;
            if(check(mid,height,tcount,n)){
                left = mid;
            }
            else 
                right = mid-1;  
        }

        if(!left){
            printf("?\n\n");
        }
        else{
            check(left,height,tcount,n,true);
            printf("\n");
        }

    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章