POJ 3080 Blue Jeans(暴力/kmp/exkmp)

题目链接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后缀作为一个完整串对其他串匹配

不同点:匹配过程所取的答案的意义不同。如图:

猜你喜欢

转载自blog.csdn.net/weixin_43768644/article/details/94543735