您的位置:首页 > 其它

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

2012-06-03 20:01 711 查看
题意:求给出的字符串中的最长公共子串 ,其中子串可以反转,比如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);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: