题目链接
题意:给出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
}
}