题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1195

其实我写麻烦了。。。

设dp[i][j]表示在i这个状态下,我们连接这些字符串且以第j个字符串为结尾所形成的字符串最短为少,g[i][j]储存这个状态下的字符串是什么。bin[i]=2^i;

那么转移方程就是dp[i|bin[k]][j]=min(dp[i|bin[k]][j],dp[i][j]+(把第k个字符串连到第j个字符串后面所会增加的长度));(j是我们枚举的i状态中已连接的字符串,k是i状态中还未连接的字符串)

很明显,那些连接任意两个字符串所要增加的长度可以事先处理出来。g数组的处理也差不多,只不过加的是把第k个字符串连到第j个字符串后面所会增加的字符串(除去公共部分)。

接下来说一些小细节:

1.首先,如果一个字符串是其他串的子串的话,就直接把它除去,我在程序中判断子串是用kmp的),为了练习一下,其实直接枚举就好了。

2.其次,我们要怎么判断两个字串相连的公共部分最长是多少呢,其实是O(n)扫一遍,这个程序里面详细写。

唔,其他细节程序里写。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<string>
#define maxx 100000000
using namespace std;
int ans,len[30][30],dp[6009][30],n,next[30],bin[30];
string a[30],b[30],g[6009][30],len2[30][30];
bool check(string&s1,string&s2)
//检查s1是不是s2的子串,我用了kmp,还是建议枚举。
{
  int l1=s1.size(),l2=s2.size();
  if (l1>l2) return false;
  int now=-1; next[0]=-1;
  for (int i=1;i<l1;i++)
  {
      while ((now!=-1)&&(s1[now+1]!=s1[i])) now=next[now];
      if (s1[now+1]==s1[i]) now++;
      next[i]=now;
  }
  now=-1;
  for (int i=0;i<l2;i++)
  {
      while ((now!=-1)&&(s2[i]!=s1[now+1])) now=next[now];
      if (s2[i]==s1[now+1]) now++;
      if (now==l1-1) return true;
  }
  return false;
}
void calc(int x,int y)
//计算第x和第y的子串,连接他们会增加的长度,和增加的字符串
{
  string s1=a[x],s2=a[y];
  int tot=0,l1=s1.size(),l2=s2.size();
  int h1=l1-1,h2=0;
  string p1,p2;
  while ((h1>=0)&&(h2<l2))
  {
      p1=s1[h1--]+p1; p2=p2+s2[h2++];
      if (p1==p2) tot=max(tot,h2);
  }
  len[x][y]=l2-tot;
  for (int i=tot;i<l2;i++) len2[x][y]=len2[x][y]+s2[i];
//这个是如果连接着两个字符串的话,在第X个字符串后面会增加的字符串是多少
}
int main()
{
  scanf("%d",&n);
  char s[100];
  for (int i=1;i<=n;i++) 
  {
    scanf("%s",s);
    string t;
    for (int j=0;j<strlen(s);j++) t=t+s[j];
    b[i]=t; 
  }
  int cnt=0;
  for (int i=1;i<=n;i++)
  {
   bool fg=false;
   for (int j=1;j<=n;j++)
   if ((i!=j)&&(check(b[i],b[j]))) 
   {
        fg=true;
        break;
   }
   if (!fg) a[++cnt]=b[i];
  }
//删去被别人包含的字符串
  n=cnt;
  if (n==0) cout<<b[1]<<endl;//如果字符串全部相同的话,就不用计算了
  else
  {
   for (int i=1;i<=n;i++)
   for (int j=1;j<=n;j++)
   if (i!=j) calc(i,j);
//计算两个字符串连接后会增加的部分
   bin[0]=1;
   for (int i=1;i<=n;i++) bin[i]=bin[i-1]*2;
   for (int i=0;i<bin[n];i++)
   for (int j=1;j<=n;j++) dp[i][j]=maxx;
   for (int i=1;i<=n;i++) 
   {
    dp[bin[i-1]][i]=a[i].size();
    g[bin[i-1]][i]=a[i];
   } 
//dp
   for (int i=0;i<bin[n];i++)
   for (int j=1;j<=n;j++)
   if ((i&bin[j-1])&&(dp[i][j]!=maxx))
   for (int k=1;k<=n;k++)
   if (!(i&bin[k-1]))
   {
      if (dp[i|bin[k-1]][k]==dp[i][j]+len[j][k])
    g[i|bin[k-1]][k]=min(g[i|bin[k-1]][k],g[i][j]+len2[j][k]);
    if (dp[i|bin[k-1]][k]>dp[i][j]+len[j][k])
    {
      dp[i|bin[k-1]][k]=dp[i][j]+len[j][k];
      g[i|bin[k-1]][k]=g[i][j]+len2[j][k];
    }
   }
   ans=maxx; string ans2;
//统计答案
   for (int i=1;i<=n;i++)
   if (dp[bin[n]-1][i]<ans)
   {
      ans=dp[bin[n]-1][i];
      ans2=g[bin[n]-1][i];
   }
   else if (dp[bin[n]-1][i]==ans)
   ans2=min(ans2,g[bin[n]-1][i]);
   cout<<ans2<<endl;
  }
  return 0;
}

 

posted on 2017-11-29 17:48  nhc2014  阅读(236)  评论(0编辑  收藏  举报