Light 1073 DNA Sequence(记忆化搜索)

题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1073

题意:给出若干个串,求一个串s使得给出的所有串都是s的子串。要求s最短;若有多个,要求字典序最小。

思路:首先,串a包含串b,则串b无需考虑。然后预处理c[i][j],表示串i有一个长为c[i][j]的后缀同时也是串j的前缀且c[i][j]不能再长了。然后记忆化搜索即可。。。一开始我以为substring是公共子列呢。。。。

 #include <iostream>
 #include <cstdio>
 #include <cstring>
 #include <algorithm>
 using namespace std;
  
 struct node
 {
     char s[105];
     int len,flag;
 };
  
 const int INF=1000000000;
 int C,num=0;
 node a[20];
 int n,f[20][(1<<16)+5],c[20][20];
 int next[20][105];
  
  
 void initKMP(char s[],int len,int next[])
 {
     next[0]=-1;
     int i=0,j=-1;
     while(i<len)
     {
         if(j==-1||s[i]==s[j]) next[++i]=++j;
         else j=next[j];
     }
 }
  
 //判断串s1中是否包含串s2
 //包含返回s2在s1的开始位置
 //不包含返回-1
 //next是s2的next数组
 int iscontained(char *s1,int len1,char *s2,int len2,int *next)
 {
     int i=0,j=0;
     while(i<len1)
     {
         if(j==-1||s1[i]==s2[j]) i++,j++;
         else j=next[j];
         if(j==len2) return i-len2;
     }
     return -1;
 }
  
 int cmp(node a,node b)
 {
     if(a.flag!=b.flag) return a.flag>b.flag;
     return strcmp(a.s,b.s)<0;
 }
  
 int cal(node a,node b)
 {
     int t=min(a.len,b.len)-1,i,j,k,p;
     for(i=t;i>=1;i--)
     {
         j=a.len-1;
         k=i-1;
         for(p=0;p<i;p++) if(a.s[j-p]!=b.s[k-p]) break;
         if(p==i) return i;
     }
     return 0;
 }
  
 void init()
 {
     int i,j,t;
     memset(c,0,sizeof(c));
     for(i=0;i<n;i++) for(j=0;j<n;j++) if(i!=j&&!c[i][j])
     {
         t=iscontained(a[j].s,a[j].len,a[i].s,a[i].len,next[i]);
         if(t==-1) continue;
         a[i].flag=0;
         c[i][j]=c[j][i]=1;
     }
     sort(a,a+n,cmp);
     for(i=0;i<n&&a[i].flag;i++);
     n=i;
     for(i=0;i<n;i++) for(j=0;j<n;j++) if(i!=j)
        c[i][j]=cal(a[i],a[j]);
 }
  
 int DFS(int cur,int st)
 {
     if(st==(1<<n)-1) return a[cur].len;
     int &ans=f[cur][st];
     if(ans!=-1) return ans;
     ans=INF;
     int next,st0;
     for(next=0;next<n;next++) if(!(st&(1<<next)))
     {
         st0=st|(1<<next);
         ans=min(ans,a[cur].len-c[cur][next]+DFS(next,st0));
     }
     return ans;
 }
  
 void print(int cur,int st)
 {
     if(st==(1<<n)-1)
     {
         printf("%s",a[cur].s);
         return;
     }
     int i,best,ans=INF,temp,next,st0;
     for(next=0;next<n;next++) if(!(st&(1<<next)))
     {
         st0=st|(1<<next);
         temp=a[cur].len-c[cur][next]+f[next][st0];
         if(temp<ans||temp==ans&&strcmp(&a[next].s[c[cur][next]],&a[best].s[c[cur][best]])<0)
         {
             ans=temp;
             best=next;
         }
     }
     for(i=0;i<a[cur].len-c[cur][best];i++) putchar(a[cur].s[i]);
     print(best,st|(1<<best));
 }
  
 void DP()
 {
     memset(f,-1,sizeof(f));
     int ans=INF,i,best,temp;
     for(i=0;i<n;i++)
     {
         temp=DFS(i,1<<i);
         if(temp<ans) ans=temp,best=i;
     }
     printf("Case %d: ",++num);
     print(best,1<<best);
     puts("");
 }
  
 int main()
 {
     for(scanf("%d",&C);C--;)
     {
         scanf("%d",&n);
         int i;
         for(i=0;i<n;i++)
         {
             scanf("%s",a[i].s);
             a[i].len=strlen(a[i].s);
             a[i].flag=1;
             initKMP(a[i].s,a[i].len,next[i]);
         }
         init();
         DP();
     }
     return 0;
 }

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值