bzoj 1195: [HNOI2006]最短母串 (状压dp)

1195: [HNOI2006]最短母串

Time Limit: 10 Sec   Memory Limit: 32 MB
Submit: 1212   Solved: 405
[ Submit][ Status][ Discuss]

Description

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

Input

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

Output

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

Sample Input

2
ABCD
BCDABC

Sample Output

ABCDABC

HINT

Source

[ Submit][ Status][ Discuss]


题解:状压dp

将答案中是否包含字符串i,状压起来。

f[i][j]表示到状态i,最前面的字符串为j 的最小连接方式的下一个串的编号

g[i][j] 表示状态i,最前面的字符串为j此时的长度

h[i][j]记录从哪个状态推来的。

转移的时候需要根据记录的后继还原出字符串进行对比

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define N 15
#define pa pair<int,int>
using namespace std;
int f[(1<<12)][13],g[(1<<12)][13],h[(1<<12)][13],can[(1<<12)][13];
int n,m,ans,c[N][N];
char a[N][100],b[N][100],str[2000],s[603],s1[603];
int len[N],len1[N],mark[N];
int calc(char a[],char b[],int len,int len1)
{
	int ans=0;
	for (int i=1;i<=len;i++)
	 {
	 	int j=1; int k=i;
	 	while (a[k]==b[j]&&k<=len&&j<=len1) k++,j++;
	 	if (k==len+1)  ans=max(ans,j-1);
	 }
	return ans;
}
bool pd(char x[],char y[],int len)
{
	for (int i=1;i<=len;i++)
	 if (x[i]<y[i])  return false;
	 else if (x[i]>y[i]) return true;
	return false;
}
int change(int x,int sta,int lenk,int last,char s[])
{
	int t;
	if (last==-1)    t=calc(s,a[x],lenk,len[x]);
	else t=c[last][x];
	for (int i=t+1;i<=len[x];i++)
	 s[++lenk]=a[x][i];
	if (f[sta][x]==0)  return lenk;
	change(f[sta][x],h[sta][x],lenk,x,s);
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		scanf("%s",b[i]+1);
		len1[i]=strlen(b[i]+1);
	}
	for (int i=1;i<=n;i++)
	 if (!mark[i])
	 for (int j=1;j<=len1[i];j++)
	  {
	  	for (int k=1;k<=n;k++)
	  	 if (k!=i)
	  	  {
	  	  	 bool f=true;
	  	  	 for (int l=1;l<=len1[k];l++)
	  	  	  if (b[i][j+l-1]!=b[k][l]){
	  	  	  	f=false;
	  	  	  	break;
				  }
			 if (f&&j+len1[k]-1<=len1[i])
			  mark[k]=1;
		  }
	  }
	int cnt=0;
	for (int i=1;i<=n;i++)
	 if (!mark[i]) {
	 	cnt++; len[cnt]=len1[i];
	 	for (int j=1;j<=len[cnt];j++)
	 	 a[cnt][j]=b[i][j];
	 }
	n=cnt; 
	for (int i=1;i<=n;i++) c[0][i]=0;
	for (int i=1;i<=n;i++)
	 for (int j=1;j<=n;j++)
	  if (i!=j)  c[i][j]=calc(a[i],a[j],len[i],len[j]);
	memset(f,-1,sizeof(f));
	queue<pa> p;
	for (int i=1;i<=n;i++)
	 f[1<<(i-1)][i]=0,g[1<<(i-1)][i]=len[i],can[1<<(i-1)][i]=1,
	 p.push(make_pair(1<<(i-1),i));
    while (!p.empty())
	   {
	   	 pa x=p.front(); p.pop(); 
	   	 int i=x.second; int sta=x.first; can[sta][i]=0;
	   	 for (int j=1;j<=n;j++)
	   	  if (!((sta>>(j-1))&1))
	   	   {
	   	   	 for (int k=1;k<=len[j];k++) s1[k]=a[j][k];
	   	   	 int t=change(i,sta,len[j],-1,s1);
	   	   	 if (f[sta|(1<<(j-1))][j]==-1)
	   	   	  {
	   	   	  	f[sta|(1<<(j-1))][j]=i; h[sta|(1<<(j-1))][j]=sta;
	   	   	  	g[sta|(1<<(j-1))][j]=t; 
				if (!can[sta|(1<<(j-1))][j])  p.push(make_pair(sta|(1<<(j-1)),j));
	   	   	  	continue;
			  }
	   	   	 int l=change(j,sta|(1<<(j-1)),0,0,s);
			 if (t<l)
			 {
			 	f[sta|(1<<(j-1))][j]=i; h[sta|(1<<(j-1))][j]=sta;
	   	   	  	g[sta|(1<<(j-1))][j]=t; 
				if (!can[sta|(1<<(j-1))][j]) p.push(make_pair(sta|(1<<(j-1)),j));
			 }
			 else 
			 if (t==l)
			  if (pd(s,s1,l))   
			  {
			    f[sta|(1<<(j-1))][j]=i,h[sta|(1<<(j-1))][j]=sta;
				if (!can[sta|(1<<(j-1))][j]) p.push(make_pair(sta|(1<<(j-1)),j));
			  }
		   }
	   }
	int t=(1<<n)-1; ans=2000;
	for (int i=1;i<=n;i++)
	 if (f[t][i]!=-1)  ans=min(ans,g[t][i]);
	for (int i=1;i<=600;i++)
	 str[i]='Z';
	for (int i=1;i<=n;i++)
	 if (g[t][i]==ans)
	 {
	  int l=change(i,t,0,0,s);
	  if (pd(str,s,ans)) 
	   for (int j=1;j<=ans;j++) str[j]=s[j];
     }
	for (int i=1;i<=ans;i++)
	 printf("%c",str[i]);
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值