题目
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;
}