DNA Laboratory POJ - 1795

题目链接
题意:给出n个字符串,构造求一个最小长度的串,该串包含给出的所有字符串。注意该字符串在长度最小的同时还必须是字典序最小。
题解:状压dp
1:首先去重,假设1个字符串包含另1个字符串就直接删去
2:预处理出任意一个字符串,放到另一个字符串所增加的长度
3: d p [ i ] [ j ] dp[i][j] dp[i][j]表示第i个放在最前面,状态为 j j j的最小值。这样的话,我们的转移方程为: d p [ i ] [ j ∣ ( 1 < < i ) ] = m i n ( d p [ i ] ] [ j ∣ ( 1 < < i ) ] , d p [ k ] [ j ] + c o s t [ i ] [ k ] ) dp[i][j|(1<<i)]=min(dp[i]][j|(1<<i)],dp[k][j]+cost[i][k]) dp[i][j(1<<i)]=min(dp[i]][j(1<<i)],dp[k][j]+cost[i][k]),这里 c o s t [ i ] [ k ] cost[i][k] cost[i][k]就是那个i放到k前面的花费
这样可以枚举到所有的情况,是状压dp常用的思路,就是先指定一个位置,然后暴力每个元素到达这个位置的情况。

最后就是找到路径,这个就是dp的常用路径寻找方式,通过dfs回溯,倒着走一遍寻找最值的路径。
下面是是AC代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <bitset>
using namespace std;
const int N = 20;
const int INF = 0x3f3f3f3f;
//思路:先求最小长度,然后再dfs回滚求字典序最小
int dp[20][(1 << 20)]; // dp[i][j]表示状态j,第i个字符串放在最前面的最小长度
int cost[20][20];      // cost[i][j]表示第i个字符串放到第j个字符串前的花费长度
string s[20], ans;
int m;
void dfs(int id, int cur) //从id开始,cur为当前的状态
{
    if (cur == 0)
        return;
    int flag = -1;
    string tmp = "zzzz";
    for (int i = 0; i < m; i++) //从前往后枚举字符串
    {
        if (i == id)
            continue;
        if ((cur & (1 << i)) && (dp[id][cur] == dp[i][cur & ~(1 << id)] + cost[id][i]))
        {
            if (s[i].substr(s[id].size() - cost[id][i]) < tmp)
            {
                tmp = s[i].substr(s[id].size() - cost[id][i]);
                flag = i;
            }
        }
    }
    if (flag != -1)
        ans += tmp;
    dfs(flag, cur & ~(1 << id));
}
int main()
{
    int t;
    scanf("%d", &t);
    int Case=0;
    while (t--)
    {
        ans="";
        int n;
        scanf("%d", &n);
        for (int i = 0; i < n; i++)
            cin >> s[i];
        //去重
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < n; j++)
            {
                if (s[i].find(s[j]) != -1)
                    s[j] = s[i];
                cost[i][i]=0;
            }
        }
        sort(s, s + n); //从小到大
        m = unique(s, s + n) - s;
        memset(cost, 0, sizeof(cost));
        //找到cost[i][j]//第i个字符串放在最前面的最小长度
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < m; j++)
            {
                if (i == j)
                    continue;
                int len = min(s[i].size(), s[j].size());
                int mx = 0; //最大的相同长度
                for (int k = s[i].size() - len; k < s[i].size(); k++)
                {
                    string s1 = s[i].substr(k); //从i开始到最后的子串
                    int midl = s1.size();
                    string s2 = s[j].substr(0, midl);
                    if (s1 == s2)
                    {
                        mx = max(mx, midl);
                    }
                }
                cost[i][j] = s[i].size() - mx;
            }
        }
        //进行状压dp
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < (1 << m); j++)
                dp[i][j] = 0x3f3f3f3f;
        }
        for (int i = 0; i < m; i++)
        {
            dp[i][(1 << i)] = s[i].size();
        }
        for (int i = 0; i < (1 << m); i++)
        {
            for (int j = 0; j < m; j++)
            {
                if ((i & (1 << j)) && dp[j][i] != INF)
                {
                    for (int k = 0; k < m; k++)
                    {
                        dp[k][i | (1 << k)] = min(dp[k][i | (1 << k)], dp[j][i] + cost[k][j]); //用当前的状态去更新其他的状态
                    }
                }
            }
        }
        int id = 0;
        for (int i = 0; i < m; i++)
        {
            if (dp[id][(1 << m) - 1] > dp[i][(1 << m) - 1])
            {
                id = i;
            }
        }
        ans += s[id];
        dfs(id, (1 << m) - 1);
        cout<<"Scenario #"<<++Case<<":"<<endl;
        cout << ans << endl<<endl;//记得多加一个endl,否则会PE
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值