前言:
这道题非常简洁且有趣。将字符串和建图完美结合在一起。
题目大意:
给你 n n n个字符串,每个字符串有一个花费 C i C_i Ci。让你将他们拼接起来形成一个回文串使得花费最小.
n ≤ 50 , ∣ s ∣ ≤ 20 , C i ≤ 1 e 9 n \leq 50 , |s| \leq 20,C_i\leq1e9 n≤50,∣s∣≤20,Ci≤1e9
题目思路:
这题过程我就理解了仨小时。。。
1.从最终答案出发:假设答案存在并且是由 k k k个字符串构成(可能重复)。 a i a_i ai序列代表下标序列.
s a 1 + s a 2 + . . . + s a k s_{a_1}+s_{a_2}+...+s_{a_k} sa1+sa2+...+sak
2.考虑将其化简成更小的问题形式(这一点很重要).
因为他是回文串,所以 s a 1 s_{a_1} sa1和 s a k s_{a_k} sak一定会有一部分是对称的。并且一定是前后缀关系:例如: a b c d abcd abcd和 b a ba ba.
那么将这个对称的部分给消除掉,一定会导致 s a 1 s_{a_1} sa1, s a k s_{a_k} sak里其中一个字符串成为空串。这里我们假设 s a k s_{a_k} sak变为空串.那么问题形式变成:
s a 1 ′ + s a 2 + . . . + s a k − 1 s_{a_1}'+s_{a_2}+...+s_{a_{k-1}} sa1′+sa2+...+sak−1, s a 1 ′ \ \ s_{a_1}' sa1′代表消去一部分前缀的 s a 1 s_{a_1} sa1.它是 s a 1 s_{a_1} sa1的后缀.
这个时候我们的整体字符串变短了,这么递归下去一定能够消除完整个回文串。
3.我们可以得到递归时每层消除字符串的做法:
只关注回文串最两端的字符串,将短的那个整个字符串给消灭掉。
4.发现这个过程这样的性质后,我们能够做什么?
4.1:不难发现,任何一个完整的回文串的构造过程都能够像上面那样逆序的消解(到最后变成一个单独的回文串或者空集).
4.2:在递归的过程中,我们总是找到当前回文串一端字符串的前缀/后缀 (这取决于你取哪一端),让它去与另一端的整个字符串进行消解。
那么我们可以利用这个递归过程来建图:
获取每一个字符串的后缀,代表它被放在左边。获取每一个字符串的前缀,代表它被放在右边。
那么这张图的节点集就是:整体字符串(它其实也属于前后缀)+他们的每一个前缀+他们的每一个后缀.(注意需要将他们的空集考虑进去)
然后对每一个[前后缀],我们都与所有 [完整后前缀] 尝试进行连边(当他们构成回文边界关系时).边权为[完整前后缀]所属的字符串的花费。
然后由于每一个[完整后缀]都有可能被放在左边第一个。所以我们开一个超级源点连接所有完整后缀(也就是整体字符串).边权为字符串的花费
再考虑到递归出口是:当我们剩下来的前后缀 s ′ s' s′是回文串时,就可以停止构造了。那么就开一个汇点。让所有本身就是回文的前后缀连向它,边权为0.
不难发现,这张图从源点跑最短路到汇点即为最终答案。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,ll>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
#define fi first
#define se second
const int maxn = 50 + 5;
const int mod = 1e9 + 7;
string a[maxn];
ll cost[maxn];
int n , tot;
map<pii,int> id[2];
map<int,pii> way[2];
vector<pii> e[1000005];
// 左边第i个字符串的第x个后缀 和 右边第j个字符串的第y个前缀 是否能匹配
// 返回{-1,-1} 代表不合法. t 表征剩下的字符是左边还是右边
pii check (pii l , pii r , int & t)
{
string x = a[l.fi].substr(l.se);
string y = a[r.fi].substr(0 , r.se + 1);
if (x.size() >= y.size()){
t = 0;
// abc ba
string pre = x.substr(0 , y.size());
reverse(pre.begin(),pre.end());
if (pre != y) return {
-1 , -1};
return {
l.fi , l.se + y.size()};
}
t = 1;
// ab cba
string aft = y.substr(y.size() - x.size() , x.size());
reverse(aft.begin(),aft.end());
if (aft != x) return {
-1 , -1};
return {
r.fi , r.se - x.size()};
}
bool check_p (int t , pii d)
{
// 特判空字符串
if (t == 0 && d.second == (int)a[d.first].size()) return true;
if (t == 1 && d.second == -1) return true;
string x;
if (t == 0) x = a[d.fi].substr(d.se);
else x = a[d.fi].substr(0 , d.se + 1);
string y = x;
reverse(y.begin(),y.end());
return x == y;
}
ll dp[1000005];
int bk[1000005];
struct Node
{
int id;
ll val;
bool operator < (const Node & a) const
{
return val > a.val;
}
};
int main()
{
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1 ; i <= n ; i++){
cin >> a[i] >> cost[i];
int m = a[i].size();
for (int t = 0 ; t <= 1 ; t++)
for (int j = 0 ; j < m ; j++)
id[t][mp(i , j)] = ++tot , way[t][tot] = mp(i , j);
// 两个空字符串
id[0][mp(i , m)] = ++tot;
way[0][tot] = mp(i , m);
id[1][mp(i , -1)] = ++tot;
way[1][tot] = mp(i , -1);
}
// 建边
++tot; // 源点 = 0 , 终点 = tot
for (int i = 1 ; i <= n ; i++){
int m = a[i].size();
for (int l = 0 ; l <= 1 ; l++){
for (int j = 0 ; j < m ; j++){
// 判断是不是回文,是的话就没必要连转移边,直接连接至终点
if (check_p(l , {
i , j})) continue;
// 否则.我们这个前缀后缀一定是连一个整串
for (int k = 1 ; k <= n ; k++){
if (i == k) continue;
int t = 0 , len = a[k].size();
pii res;
if (l) res = check({
k , 0} , {
i , j} , t);
else res = check({
i , j} , {
k , len - 1} , t);
if (res.fi == -1) continue;
e[id[l][{
i , j}]].pb({
id[t][res] , cost[k]});
// 输出相关信息
/* if (l) cout << "右边 ";
else cout << "左边 ";
cout << i << " " << j << ' ';
cout << "成功与第" << k << "个字符串连上一条边" << endl;
cout << "他们将转移到" << res.first << " " << res.second << endl;*/
}
}
}
}
// 源点连 整左字符串
for (int i = 1 ; i <= n ; i++) e[0].pb({
id[0][{
i , 0}] , cost[i]});
// 回文串连 终点
for (int i = 0 ; i <= 1 ; i++){
for (auto g : way[i]){
if (check_p(i , g.se)) e[g.fi].pb({
tot , 0});
}
}
priority_queue<Node> q;
memset(dp , -1 , sizeof dp);
dp[0] = 0;
q.push({
0 , 0});
while (q.size()){
int u = q.top().id; q.pop();
if (bk[u]) continue;
bk[u] = 1;
for (auto g : e[u]){
int v = g.fi;
ll w = g.se;
if (dp[v] == -1 || dp[v] > dp[u] + w){
dp[v] = dp[u] + w;
q.push({
v , dp[v]});
}
}
}
cout << dp[tot] << endl;
return 0;
}