题目链接:http://poj.org/problem?id=3080
解题思路:
找给出的n个串中最长相同部分,长度相同就选择字典序最小的。
①暴力法
抠出第一个串所有子串排序,第一关键字为长度,第二关键字为字典序
让长度更长,字典序更小的排前面
然后暴力在其他几个串中找有无这个子串,都存在就直接输出这个。
(1)可以使用string.find(子串),未找到返回string::npos,不过这个函数好像就是O(N*M)复杂度的暴力搜索
(2)使用strstr(其他串,子串),未找到返回false,这个内部实现不太清楚不过比find()快一点,但是两个参数都是char*,利用string.c_str()可以转化为char*型
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<set>
#define debug(x) cout << "--Line" << #x << "--\n";
using namespace std;
const int N = 60+5;
struct node
{
string s;
bool operator < (const node& a) const{
if (s.length() == a.s.length()) return s<a.s;
return s.length() > a.s.length();
}
};
set<node>se;
string ss[15];
char s1[65],s2[65];
int main()
{
std::ios::sync_with_stdio(false);
int T;
cin >> T;
while (T--){
se.clear();
int n;
cin >> n;
string a;
cin >> a;
//获取A串的所有子串
for (int i=0;i<a.length();i++){
for (int j=1;j<=a.length()-i;j++){
string t = a.substr(i,j);
se.insert({t});
}
}
for (int i=1;i<n;i++) cin >> ss[i];
bool output = false;
for (set<node>::iterator it = se.begin();it!=se.end();it++){
bool flag = true;
for (int i=1;i<n;i++)
if (strstr(ss[i].c_str(),it->s.c_str()) == false){flag = false;break;}//if (ss[i].find(it->s)==string::npos){flag = false;break;}
if (flag){
if (it->s.length()<3) cout << "no significant commonalities" <<endl;
else cout << it->s << endl;
output = true;
break;
}
}
if (!output) cout << "no significant commonalities" <<endl;
}
return 0;
}
②KMP
抠出第一个串所有后缀,构造失配数组,然后匹配剩余串,每次匹配取匹配过程中j的最大值max1(表示匹配过程中最长相同部分),对于这n-1匹配的最大值取最小值min2表示公有的最长部分。
对于每抠出来的后缀进行这样一个操作,取min2的最大值max3就是最长长度。
在更新min2,max3的时候同步更新答案
核心思想:串1的后缀匹配其他串找最长相同部分
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#define debug(x) printf("---Line %s ---\n",#x)
#define for1(i,x) for (int i = 1;i <= x; i++)
#define for0(i,x) for (int i = 0;i < x; i++)
using namespace std;
const int N = 70;
int fail[N];
char s[N],ss[10][N];
int max1,min2,max3;
char ans2[70],ans3[70];
void GETFAIL(char *s)
{
int len = strlen(s);
fail[0] = -1;
for (int i=0,j=-1;i<len;){
if (j==-1||s[i]==s[j]){
i++;
j++;
fail[i] = j;
}
else j = fail[j];
}
}
void KMP(char *s,char *ss)
{
GETFAIL(ss);
int len = strlen(s);
for (int i=0,j=0;i<len;){
if (j==-1||s[i]==ss[j]){
i++;j++;
}
else j = fail[j];
if (j > max1) max1 = j;
}
}
int main()
{
int T;
scanf("%d",&T);
while (T--){
int n;
scanf("%d",&n);
scanf("%s",s);
for1(i,n-1) scanf("%s",ss+i);
max3 = -1;
for0(i,60){
min2 = 100;
for1(j,n-1){
max1 = -1;
KMP(ss[j],s+i);///找n-1个主串所有后缀 和 S串最长匹配部分 的最大值max1
if (min2 > max1){
min2 = max1;
for (int k = i;k<i+min2;k++)
ans2[k-i] = s[k];
ans2[min2] = '\0';
} //处理ans1,去掉多余的部分,变成ans2
}
if (min2>max3){
max3 = min2;
int len = strlen(ans2);
for (int i=0;i<len;i++)
ans3[i] = ans2[i];
ans3[len] = '\0';
}//把ans3改为ans2
else if (min2==max3){
if (strcmp(ans3,ans2)>0){
int len = strlen(ans2);
for (int i=0;i<len;i++)
ans3[i] = ans2[i];
ans3[len] = '\0';
}
} //比较字典序
}
if (max3 < 3) printf("no significant commonalities\n");
else printf("%s\n",ans3);
}
return 0;
}
③exKMP
还是抠出后缀,构造next数组,与其他串匹配构造ex数组取ex数组最大值max1表示抠出后缀与其他某一串的某一后缀的最长相同长度,取每次max1的最小值min2表示当前抠出后缀在其他串中共有的最长长度,某个抠出的后缀的min2取最大值max3就是答案
核心思想:串1后缀与其他串匹配找出最长相同部分
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#define debug(x) printf("---Line %s ---\n",#x)
#define for1(i,x) for (int i = 1;i <= x; i++)
#define for0(i,x) for (int i = 0;i < x; i++)
using namespace std;
const int N = 70;
int nt[N];
int ex[N];
char s[70],ss[10][70];
int max1,min2,max3;
char ans2[70],ans3[70];
//char ans1[70];
void GETNEXT(char *str)
{
int i=0,j,po,len=strlen(str);
nt[0]=len;
while(str[i]==str[i+1]&&i+1<len) i++;
nt[1]=i;
po=1;
for(i=2;i<len;i++)
{
if(nt[i-po]+i<nt[po]+po)
nt[i]=nt[i-po];
else
{
j=nt[po]+po-i;
if(j<0)j=0;
while(i+j<len&&str[j]==str[j+i])
j++;
nt[i]=j;
po=i;
}
}
}
void EXKMP(char *s1,char *s2)
{
/*************************/
//int pos = -1;
/*************************/
int i=0,j,po,len=strlen(s1),l2=strlen(s2);
GETNEXT(s2);
while(s1[i]==s2[i]&&i<l2&&i<len)
i++;
ex[0]=i;
/*************************/
if (ex[0]>max1) max1 = ex[0];
/*************************/
po=0;
for(i=1;i<len;i++)
{
if(nt[i-po]+i<ex[po]+po){
ex[i]=nt[i-po];
/*************************/
if (ex[i]>max1) max1 = ex[i];
/*************************/
}
else
{
j=ex[po]+po-i;
if(j<0)j=0;
while(i+j<len&&j<l2&&s1[j+i]==s2[j])
j++;
ex[i]=j;
po=i;
/*************************/
if (ex[i]>max1) max1 = ex[i];
/*************************/
}
}
/*
///同一前缀的,只要记长度就行,最后一次赋值,ans1完全可以不要
for (int i=0;i<max1;i++)
ans1[i] = s2[i];
ans1[max1] = '\0';
*/
}
int main()
{
int T;
scanf("%d",&T);
while (T--){
int n;
scanf("%d",&n);
scanf("%s",s);
for1(i,n-1) scanf("%s",ss+i);
max3 = -1;
for0(i,60){
min2 = 100;
for1(j,n-1){
max1 = -1;
EXKMP(ss[j],s+i);///找n-1个主串所有后缀 和 S串前缀作为的子串的 最长重合部分 的最大值max1
if (min2 > max1){
min2 = max1;
for (int k = i;k<i+min2;k++)
ans2[k-i] = s[k];
ans2[min2] = '\0';
} //处理ans1,去掉多余的部分,变成ans2
}
if (min2>max3){
max3 = min2;
int len = strlen(ans2);
for (int i=0;i<len;i++)
ans3[i] = ans2[i];
ans3[len] = '\0';
}//把ans3改为ans2
else if (min2==max3){
if (strcmp(ans3,ans2)>0){
int len = strlen(ans2);
for (int i=0;i<len;i++)
ans3[i] = ans2[i];
ans3[len] = '\0';
}
} //比较字典序
}
if (max3 < 3) printf("no significant commonalities\n");
else printf("%s\n",ans3);
}
return 0;
}
④KMP与exKMP方法的对比
相同点:都将串1后缀作为一个完整串对其他串匹配
不同点:匹配过程所取的答案的意义不同。如图: