poj 1226 求多串最长公共子串 或 回文子串 KMP&&strstr&&后缀数组

题意:求给出的字符串中的最长公共子串 ,其中子串可以反转,比如rose 和 orchid , ro 和第一个匹配 , ro 的回文串or 和orchid 匹配 。最后输出最长子串的长度。

思路:找出长度最小的子串, 枚举其所有子串 ,然后将其反转再保存 ,最后用这两个串去匹配其它的字符串。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char str[52][505];
char ss[305] , ts1[305] , ts2[305];
int next[205];
void get_next(char *T , int len) {      /*****得到next数组*****/ 
    int j = 0, k = -1;
    next[0] = -1;
    while (j < len) {
        if(T[j] == T[k] || k == -1) {
            next[j+1] = k+1;
            j++;k++;    
        }
        else k = next[k];    
    }    
}
int kmp(char *T , int h , int len2 , int len1) { /*****KMP*****/
    int i , j;
    i = j = 0; 
    while(i <len1 && j <len2) {
        if(T[j]==str[h][i] || j == -1) {
            j++;i++;    
        }else {
        j = next[j];
        }
    } 
    if(j == len2) return 1;
    return 0;   
}
int main() {
    int T , i , j , k , len , n , g , temp;
    scanf("%d",&T);
    while(T--) {
        scanf("%d",&n);
        for(i = 0 ; i < n ; i ++)
        scanf("%s",str[i]);
        len = 120;
        for(i = 0 ; i < n ; i ++) {         /*****找长度最小的字符串*****/ 
            if(strlen(str[i]) <= len) {
                len = strlen(str[i]);
                temp = i;    
            }    
        }
        int tt , ans , cnt , l1 , l2 , lenn , f , flag , r , t;
        ans = 0;
        for(i = 1 ; i <= len ; i ++) {
            for(j = 0 ; j <= len - i; j ++) {
                for(g = 0 ,k = j ; k < j + i ; k ++ , g++) { /*****暴力枚举长度是i的最小字符串子串ts1*****/ 
                    ts1[g] = str[temp][k];            
                }
                ts1[g] = '\0';
               for(tt = 0 ; tt < g ; tt ++) {                 /*****求这个子串的反转子串ts2*****/ 
                    ts2[tt] = ts1[g - tt - 1];    
                }
                ts2[tt] = '\0';
                get_next(ts1 , i);                             /*****得到ts1的next数组*****/ 
                cnt = 0;
                bool vis[100];                             /*****标记数组*****/ 
                memset(vis , false , sizeof(vis));
                for(f = 0 ; f < n; f ++) {
                    lenn = strlen(str[f]);
                    if(kmp(ts1 , f , i , lenn) && !vis[f]) {
                        vis[f] = 1; cnt++;} 
                }
                get_next(ts2 , i);
                for(f = 0 ; f < n ; f ++) {
                    lenn = strlen(str[f]);
                    if(kmp(ts2 , f , i , lenn)&&!vis[f]) {
                        vis[f] = 1; cnt++;} 
                }                                              /*****分别用ts1 、 ts1 去匹配其它串*****/ 
                if(cnt == n) {                                 //满足条件中取最长的子串长度 
                    if(i > ans) ans = i;    
                }
            }    
        }  
        printf("%d\n",ans); 
    }    
}

strstr 函数水的~   注意strncpy枚举子串的方法 

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char str[120][120];
char s1[120] , s2[120];
char zz1[120] , zz2[120];
int main() {
    int n , T , i , j , k , t , temp;
    scanf("%d",&T);
    while(T--) {
        scanf("%d",&n);
        int len = 120;
        for(i = 0 ; i < n ; i ++) {
            scanf("%s",str[i]);
            int ll = strlen(str[i]);
            if(ll < len) {
                len = ll;
                temp  = i;
            }  
        }
        strcpy(s1 , str[temp]);
        int ans = 0;
        for(i = 1 ; i <= len ; i ++) { //枚举子串长度
            for(j = 0 ; j <= len - i ; j ++) {
                strncpy(zz1, s1+j , i);
                zz1[i] = '\0';      //子串 
                //printf("%s\n",zz1);
                for(k = 0 ; k < i ; k ++)
                zz2[k] = zz1[i - k - 1];
                zz2[k] = '\0';     //反转子串 
                //printf("%s\n",zz2);
                for(t = 0 ; t < n ; t ++) {
                    if(strstr(str[t] , zz1)||strstr(str[t] , zz2)) continue;
                    else break;    
                }
                if(t == n) ans = i>ans?i:ans; 
            }   
        }
        printf("%d\n",ans);     
    }    
}


后缀数组的方法:和求多串公共子串差不多的方法,不过这题要连接的不只给出的字符串,也要把给出的子串的反转串连接起来,并且loc数组的状态要和正串是一样的。

最后二分枚举最短字符串的子串长度,判断即可。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 21000;
char str[maxn];
int num[maxn] , loc[maxn];
int sa[maxn] , rank[maxn] , height[maxn];
int wa[maxn] , wb[maxn] , wv[maxn] , wd[maxn];
int n;
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 n , int m) {
    int i , j , p;
    int *x = wa , *y = wb , *t;
    for(i = 0 ; i < m ; i ++) wd[i] = 0;
    for(i = 0 ; i < n ; i ++) wd[x[i]=r[i]]++;
    for(i = 1 ; i < m ; i ++) wd[i] += wd[i-1];
    for(i = n-1 ; i >= 0 ; i --) sa[--wd[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 ++) wd[i] = 0;
        for(i = 0 ; i < n ; i ++) wd[wv[i]] ++;
        for(i = 1 ; i < m ; i ++) wd[i] += wd[i-1];
        for(i = n - 1 ; i >= 0 ; i --) sa[--wd[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 ++;        
        }   
    }    
}
void calheight(int *r , 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 vis[1300];
bool check(int mid , int len) {
    int i , j , cnt;
    cnt = 0;
    memset(vis , 0 , sizeof(vis));
    for(i = 2 ; i <= len ; i ++) {
        //printf("%d\n",height[i]);
        if(height[i] < mid) {
            memset(vis,0,sizeof(vis));
            cnt = 0;
            continue;    
        }    
        if(!vis[loc[sa[i-1]]]) {
            vis[loc[sa[i-1]]] = 1;
            cnt ++;        
        }
        if(!vis[loc[sa[i]]]) {
            vis[loc[sa[i]]] = 1;
            cnt ++;    
        }
        if(cnt == n) return 1;
    }
    return 0;    
}
int main() {
    int T , i , j , g , k , temp;
    scanf("%d",&T);
    while(T--) {
        scanf("%d",&n);
        g = 0;
        temp = 130;
        int ll = 120;
        for(i = 0 ; i < n ; i ++) {
            scanf("%s",str);
            int len = strlen(str);
            if(len < ll) ll = len;
            for(j = 0 ; j < len ; j ++) {
                loc[g] = i;    
                num[g++] = str[j];
            }
            loc[g] = temp;
            num[g++] = temp++;
            for(j = 0 ; j < len ; j ++) {
                loc[g] = i;    
                num[g++] = str[len - j - 1];
            }
            loc[g] = temp;
            num[g++] = temp ++;    
        }   
        num[g] = 0;
        da(num , g+1 , temp);
        calheight(num , g);
        int left , right , mid , ans;
        left = 0;right = ll;
        while(left <= right) {
            mid = (left+right)/2;
            if(check(mid , g)) {
                left = mid + 1;
                ans = mid;        
            } else {
                right = mid - 1;
                //ans = mid;    
            }
        }
        printf("%d\n",ans);
    }       
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值