题解 P2322 【[HNOI2006]最短母串问题】

(Large exttt{P2322})

标签:状压DP+暴力

题意

给你n个字符串,要求把它们按一定顺序首尾拼接起来,若拼接的两个字符串中左边的字符串末尾和右边字符串的首头部有公共部分,则可以重合这部分。

求最短的n个字符串拼接而成的字符串,并要求字典序最小

(nle12,lenle 50)

思路

因为题目中的 (n) 很小,并且每个字符串的长度也很小,那就可以暴力预处理出任意两个字符串拼接而成能省掉多少的字符(建议使用 ( exttt{string})黑科技),令数组 (v[i][j]) 表示字符串 (j) 接在字符串 (i) 后面减少的贡献。(就是这么暴力)

这里复杂度大概是 (O(N^2M))(M) 为字符串长度)

然后就是最重要的状压部分,令数组 (dp[i][j]) 为在状态 (i) (每个字符串是否使用)下,这个字串符最左边的字符串为 (j) 的最优长度,状态转移方程可以容易想到:

[dp[i][j]=min(dp[ioplus (1<<(j-1))][k]+len[j]-v[j][k]) ]

这里的复杂度大概是 (O(2^NN^2))

上面忽略了STL的复杂度


但是还没完,上面的代码打完连样例都过不去,因为没有按字典序/fad

其实可以再加一个数组 (p) , (p[i][j]) 的定义和 (dp) 一样,但是是拿来维护 (dp[i][j]) 最优时的字串符,当 (dp[i][j]) 与当前的待比较的值相同时,比较两者字符串字典序并修改 (p[i][j])(这边建议( exttt{string})黑科技)。

代码

耗时 (86ms)

为啥我都是暴力了还是最优解第二啊

#include <bits/stdc++.h>
using namespace std;

const int N = 12;
inline int read()
{
    register int s = 0;
    register bool neg = 0;
    register char c = getchar();
    for (; c < '0' || c > '9'; c = getchar())
        neg |= (c == '-');
    for (; c >= '0' && c <= '9'; s = s * 10 + (c ^ 48), c = getchar())
        ;
    return (neg ? -s : s);
}

int a, dp[1 << N][N + 10], v[N + 10][N + 10];
string s[N + 10], p[1 << N][N + 10];

inline int calc(int n, int m)
{
    int ans = 0;
    for (int i = 0; i < min(s[n].size(), s[m].size()); i++)
    {
        if (s[n].substr(s[n].size() - i - 1, i + 1) == s[m].substr(0, i + 1))
            ans = i + 1;
    }
    return ans;
}

signed main()
{
    a = read();
    for (int i = 1; i <= a; i++)
        cin >> s[i];
    for (int i = 1; i <= a; i++)
        for (int j = 1; j <= a; j++)//暴力预处理
            if (i != j)
                v[i][j] = calc(i, j);
    const int mx = (1 << a) - 1;
    memset(dp, 0x3f, sizeof(dp));
    for (int i = 1; i <= mx; i++)//状压
    {
        for (int j = 1; j <= a; j++)
        {
            if (i & (1 << (j - 1)))
            {
                int tmp = (i ^ (1 << (j - 1)));
                if (!tmp)
                {
                    dp[i][j] = s[j].size();
                    p[i][j] = s[j];
                    continue;
                }
                for (int k = 1; k <= a; k++)
                {
                    if (tmp & (1 << (k - 1)))
                    {
                        int tt = dp[tmp][k] + s[j].size() - v[j][k];
                        if (dp[i][j] > tt)
                        {
                            dp[i][j] = tt;
                            p[i][j] = s[j].substr(0, s[j].size() - v[j][k]) + p[tmp][k];
                        }
                        else if (dp[i][j] == tt)//特判,选最优字典序
                        {
                            string q = s[j].substr(0, s[j].size() - v[j][k]) + p[tmp][k];
                            if (p[i][j] > q)
                                p[i][j] = q;
                        }
                    }
                }
            }
        }
    }
    int ans = 1;
    for (int i = 2; i <= a; i++)
        if (dp[mx][ans] > dp[mx][i] || (dp[mx][ans] == dp[mx][i]) && (p[mx][i] < p[mx][ans]))//注意字典序最优
            ans = i;
    cout << p[mx][ans];
    return 0;
}