大视野1195: [HNOI2006]最短母串

题目

Description


给定n个字符串(S1,S2,„,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,„,Sn)都是T的子串。

Input

第一行是一个正整数n(n<=12),表示给定的字符串的个数。以下的n行,每行有一个全由大写字母组成的字符串。每个字符串的长度不超过50.

Output

只有一行,为找到的最短的字符串T。在保证最短的前提下,如果有多个字符串都满足要求,那么必须输出按字典序排列的第一个。

题意

题意非常简单,就是题目第一行。

解法

(S1,S2,„,Sn)之间是通过共用字符的方式来减少最后S的长度,那么遍历(S1,S2,„,Sn)的所有全排列就能找到那个最短的,这让人想到哈密顿路径(不是回路,起始点不同),用动态规划可以有O(n^2*2^n)的复杂度比O(n!)好一点。具体的是,建图mp[i][j]表示当第i个串在第j个串前面时他们能共用的最大字符个数。然后dp[i][j]表示当前位置在i,已经经过点集j(j是用二进制压缩了的状态量)的最大路的长度,因为题目还要求字典序最小的,所以我的dp[i][j]的结果是一个结构体,把路径和长度都保存了。

代码

#include<cstdio>
#include<cstring>
struct node
{
    int val,path[15],cnt;
}dp[15][5000];//val表示路径的长度,cnt表示路径上节点的数量,path记录了具体的节点
char str[15][55],str1[700],str2[700],ans[700];//str是原始的数据,str1和str2是用path求构成的字符串时用的,ans是最后答案
int mp[15][15],len[15];//len[i]是输入的第i个字符串的长度
int cal(int k1,int k2)//算mp[k1][k2]的值
{
    int i,j,ans=0;
    for(i=0;i<=len[k1]&&i<=len[k2];i++)
    {
        for(j=0;j<i;j++)
        {
            if(str[k1][len[k1]-j-1]!=str[k2][i-j-1])
                break;
        }
        if(i==j)
            ans=i;
    }
    return ans;
}
int check(int k1,int l1,int k2,int l2,int flag)//计算dp[k1][l1]和dp[k2][l2]所保存的路径翻译成字符串后字符串的字典序大小关系,flag是1的时候dp[k1][l1]的路径还要加上k2点,具体看调用那里
{
    int i,len1=0,len2=0;
    int tmp=dp[k1][l1].path[0];
    while(str[tmp][len1]!=0)
    {
        str1[len1]=str[tmp][len1];
        len1++;
    }
    for(i=1;i<dp[k1][l1].cnt;i++)
    {
        tmp=dp[k1][l1].path[i];
        int pos=mp[dp[k1][l1].path[i-1]][tmp];
        while(str[tmp][pos]!=0)
            str1[len1++]=str[tmp][pos++];
    }
    tmp=dp[k2][l2].path[0];
    while(str[tmp][len2]!=0)
    {
        str2[len2]=str[tmp][len2];
        len2++;
    }
    for(i=1;i<dp[k2][l2].cnt;i++)
    {
        tmp=dp[k2][l2].path[i];
        int pos=mp[dp[k2][l2].path[i-1]][tmp];
        while(str[tmp][pos]!=0)
            str2[len2++]=str[tmp][pos++];
    }
    if(flag)
    {
        int pos=mp[k2][k1];
        while(str[k1][pos]!=0)
            str2[len2++]=str[k1][pos++];
    }
    str1[len1]=0;
    str2[len2]=0;
    if(strcmp(str1,str2)<=0)
        return 0;
    else
        return 1;
}
int main()
{
    int n,i,j,k;
    scanf("%d",&n);
    for(i=0;i<n;i++)
    {
        scanf("%s",str[i]);
        len[i]=strlen(str[i]);
    }
    for(i=0;i<n;i++)
        for(j=0;j<n;j++)
        {
            if(i==j)
                continue;
            mp[i][j]=cal(i,j);
        }
    int lim=(1<<n);
    for(i=0;i<n;i++)
        for(j=0;j<lim;j++)
            dp[i][j].val=-1;
    for(i=0;i<n;i++)
    {
        dp[i][1<<i].val=0;
        dp[i][1<<i].cnt=1;
        dp[i][1<<i].path[0]=i;
    }
    for(j=1;j<lim;j++)
    {
        for(i=0;i<n;i++)
        {
            if(dp[i][j].val!=-1)
            {
                for(k=0;k<n;k++)
                {
                    if(((j>>k)&1))
                        continue;
                    int tmp=(j|(1<<k));
                    if(dp[k][tmp].val<dp[i][j].val+mp[i][k]||((dp[k][tmp].val==dp[i][j].val+mp[i][k])&&check(k,tmp,i,j,1)))//这里是check函数flag=1的调用,这里是在长度相同情况下判断是否要从点i转移到点k
                    {           
                        dp[k][tmp]=dp[i][j];
                        dp[k][tmp].val=dp[i][j].val+mp[i][k];
                        dp[k][tmp].path[dp[k][tmp].cnt++]=k;
                    }
                }
            }
        }
    }
    int pp=0;
    node res;
    res.val=-1;
    lim--;
    for(i=0;i<n;i++)
    {
        if(dp[i][lim].val>res.val||(dp[i][lim].val==res.val&&check(pp,lim,i,lim,0)))
        {
            res=dp[i][lim];
            pp=i;
        }
    }
    int tmp=res.path[0],lenth=0;
    while(str[tmp][lenth]!=0)
    {
        ans[lenth]=str[tmp][lenth];
        lenth++;
    }
    for(i=1;i<res.cnt;i++)
    {
        tmp=res.path[i];
        int pos=mp[res.path[i-1]][tmp];
        while(str[tmp][pos]!=0)
            ans[lenth++]=str[tmp][pos++];
    }
    ans[lenth]=0;
    puts(ans);
    return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值