BZOJ1195 [HNOI2006]最短母串 【状压dp】

题目

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

输入格式

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

输出格式

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

输入样例

2

ABCD

BCDABC

输出样例

ABCDABC

题解

写完状压dp后才知道可以用AC自动机水,而且十分简洁。。。

我还是说状压dp吧。。
思想简单却十分难写,,,

我们先将其它串的子串去掉,保证两两完全不包含
\(f[s][i]\)表示当前串集合为\(s\),最后一个为\(i\)号串的最小长度,同时记录一个\(pre\)数组记录状态转移的方向
再预处理一个\(at[i][j]\)表示在\(i\)串后接\(j\)串增加的长度

转移十分显然,枚举下一个不在集合中的串,如果方案更优则转移,如果同样优,利用pre数组还原串比较字典序

所有的字符串比较之类的都可以直接暴力

虽然代码长了一点丑了一点,但是跑得飞快

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define ULL unsigned long long int
#define LL long long int
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<' '; puts("");
#define mp(a,b) make_pair<int,int>(a,b)
#define cp pair<int,int>
using namespace std;
const int maxn = 15,maxm = (1 << 13),maxl = 55,INF = 1000000000;
struct String{
    char s[maxl];
    int len;
    ULL h[maxl];
}S[maxn];
cp pre[maxm][maxn];
int n,at[maxn][maxn],f[maxm][maxn];
ULL P[1000];
void exclude(){
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= n; j++){
            if (j != i && S[j].len >= S[i].len){
                int flag = false,L = S[j].len - S[i].len + 1;
                for (int k = 1; k <= L; k++){
                    if (S[i].h[S[i].len] == S[j].h[k + S[i].len - 1] - S[j].h[k - 1] * P[S[i].len]){
                        flag = true;
                        break;
                    }
                }
                if (flag){
                    swap(S[i--],S[n--]);
                    break;
                }
            }
        }
    }
    //for (int i = 1; i <= n; i++)
    //  printf("%s\n",S[i].s + 1);
}
void init(){
    REP(i,n) REP(j,n) if (i != j){
        for (int k = max(1,S[i].len - S[j].len + 1); k <= S[i].len; k++){
            int L = S[i].len - k + 1;
            if (S[j].h[L] == S[i].h[S[i].len] - S[i].h[k - 1] * P[L]){
                at[i][j] = L;
                break;
            }
        }
    }
    //REP(i,n) printf("%d ",at[1][i]); puts("");
}
char t1[1000],t2[1000];
int s1[55],s2[55],top1,top2,n1,n2;
int cmp(int a,int b,int c,int d,int x){
    top1 = 0;
    while (a && b){
        s1[++top1] = b;
        cp tmp = pre[a][b];
        a = tmp.first;
        b = tmp.second;
    }
    n1 = 0;
    for (int i = top1,last = 1; i; i--){
        int u = s1[i];
        for (int j = last; j <= S[u].len; j++){
            t1[++n1] = S[u].s[j];
        }
        last = at[u][s1[i - 1]] + 1;
    }
    if (x){
        int l = at[s1[1]][x] + 1;
        for (int i = l; i <= S[x].len; i++)
            t1[++n1] = S[x].s[i];
    }
    top2 = 0;
    while (c && d){
        s2[++top2] = d;
        cp tmp = pre[c][d];
        c = tmp.first;
        d = tmp.second;
    }
    n2 = 0;
    for (int i = top2,last = 1; i; i--){
        int u = s2[i];
        for (int j = last; j <= S[u].len; j++){
            t2[++n2] = S[u].s[j];
        }
        last = at[u][s2[i - 1]] + 1;
    }
    if (x){
        int l = at[s2[1]][x] + 1;
        for (int i = l; i <= S[x].len; i++)
            t1[++n2] = S[x].s[i];
    }
    for (int i = 1; i <= n1; i++)
        if (t1[i] != t2[i]) return t2[i] - t1[i];
    return 0;
}
void print(int x){
    int a = (1 << n) - 1,b = x;
    top1 = 0;
    //puts("");
    while (a && b){
        //puts(S[b].s + 1);
        s1[++top1] = b;
        cp tmp = pre[a][b];
        a = tmp.first;
        b = tmp.second;
    }
    n1 = 0;
    for (int i = top1,last = 1; i; i--){
        int u = s1[i];
        for (int j = last; j <= S[u].len; j++){
            t1[++n1] = S[u].s[j];
        }
        last = at[u][s1[i - 1]] + 1;
    }
    t1[n1 + 1] = '\0';
    printf("%s\n",t1 + 1);
}
void solve(){
    fill(f[0],f[0] + maxm * maxn,INF);
    int maxv = (1 << n) - 1;
    for (int i = 1; i <= n; i++) f[1 << i - 1][i] = S[i].len;
    for (int s = 0; s <= maxv; s++){
        for (int i = 1; i <= n; i++){
            if (!(s & (1 << i - 1))) continue;
            for (int j = 1; j <= n; j++){
                if ((s | (1 << j - 1)) != s){
                    int e = (s | (1 << j - 1)),len = S[j].len - at[i][j];
                    if (f[e][j] > f[s][i] + len){
                        f[e][j] = f[s][i] + len;
                        pre[e][j] = mp(s,i);
                    }
                    else if (f[e][j] == f[s][i] + len){
                        int tmp = cmp(s,i,pre[e][j].first,pre[e][j].second,j);
                        if (tmp > 0) pre[e][j] = mp(s,i);
                    }
                }
            }
        }
    }
    int ans = 1;
    for (int i = 2; i <= n; i++){
        if (f[maxv][i] < f[maxv][ans] || (f[maxv][i] == f[maxv][ans] && cmp(maxv,i,maxv,ans,0) > 0)){
            ans = i;
        }
    }
    print(ans);
}
int main(){
    P[0] = 1;
    for (int i = 1; i < 1000; i++) P[i] = P[i - 1] * 107;
    scanf("%d",&n);
    for (int i = 1; i <= n; i++){
        scanf("%s",S[i].s + 1);
        S[i].len = strlen(S[i].s + 1);
        for (int j = 1; j <= S[i].len; j++){
            S[i].h[j] = S[i].h[j - 1] * 107 + S[i].s[j] - 'A';
        }
    }
    exclude();
    init();
    solve();
    return 0;
}

转载于:https://www.cnblogs.com/Mychael/p/8795260.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值