思路:
因为是回文子串,所以肯定枚举中间切割点,当前中间切割点的最长回文子串是保留的。
比如jelly,如果遍历到ll中间的空隙作为切割点的话,ll是肯定保留的。
然后就可以分成两种情况
右边全部删掉+左边部分删+左边剩下补右
左边全部删掉+右边部分删+右边剩下补左
比如 jelly 以e为切割点
右边全部删掉:je
左边部分删掉+左边剩下补右
左边删掉0个,右边补j:jej
左边删掉1个,右边不用补:e
左边全部删掉:elly
右边部分删掉+右边剩下补左
右边删掉0个,左边补lly:yllelly
右边删掉1个,左边补ll: llell(这个方法就是案例的答案)
右边删掉2个,左边补l:lel
……………
所以根据上面分析,大体可以分钟两大种情况,就是一边全部删掉,另一边继续分类。从第i个点的左边或者右边全部删掉的话,直接记录删掉的前缀和就可以了。
然后问题就来到,怎么处理部分删掉,需要补的那部分?
因为两种大情况是对称的,所以下面就以右边全部删掉,处理左边部分删掉为例:
观察可以得知,一个字母只有两种情况,加上或者删掉。
当前删掉:因为删掉只能从头或者尾开始删,所以当前删掉的值就是dl[i]
Pre[i]:对 I 及其前缀进行处理需要花费的最小价值
当前加上:当前加上,前面的字符串可以删掉也可以加上,所以我们这里另外设置一个数组来存储操作的最小值pre[i] = pre[I - 1] + 当前字母保留的价值。
和dl[i]更新pre[i] 。
最后我们只需要遍历中间切割点,然后找出需要处理的左端点和右端点,分两种大情况处理就可以了,具体看代码实现。
实现:
#include <stdio.h>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
char s[N], t[2 * N];
int p[2 * N], n;
ll val[27][2], dl[N], dr[N], pre[N], suf[N];
int init(char *s) //修饰字符串
{
int slen = strlen(s + 1);
int len = 1;
t[len++] = '$';
t[len++] = '#';
for (int i = 1; i <= slen; i++)
{
t[len++] = s[i];
t[len++] = '#';
}
t[len] = '&';
return len;
}
/**
* 功能:找出/输出 最长回文子串
* t,修饰后的字符串,len修饰后字符串的长度
* 返回值: 修改后字符串的长度
*/
void manacher(char *t, int len)
{
int rmid = 0, rboun = 0;
for (int i = 1; i <= len; i++)
{
int j = 2 * rmid - i;
if (rboun > i)
p[i] = min(rboun - i, p[j]);
else
p[i] = 0;
//暴力向两边拓展
while (t[i - 1 - p[i]] == t[i + 1 + p[i]])
p[i]++;
//更新
if (i + p[i] > rboun)
{
rmid = i;
rboun = i + p[i];
}
}
}
int main()
{
scanf("%s", s + 1);
for (int i = 1; i <= 26; i++)
// 0删掉,1加
scanf("%lld%lld", &val[i][0], &val[i][1]);
n = strlen(s + 1);
//找出前缀最小值
for (int i = 1; i <= n; i++)
{
dl[i] = dl[i - 1] + val[s[i] - 'a' + 1][0];
//第i个保留
pre[i] = pre[i - 1] + val[s[i] - 'a' + 1][1];
//第i个删掉
pre[i] = min(pre[i], dl[i]);
}
for (int i = n; i > 0; i--)
{
dr[i] = dr[i + 1] + val[s[i] - 'a' + 1][0];
suf[i] = suf[i + 1] + val[s[i] - 'a' + 1][1];
suf[i] = min(suf[i], dr[i]);
}
int len = init(s);
manacher(t, len);
ll res = 1e18;
for (int i = 3; i < len; i++)
{
//#
if (i % 2 == 0)
{
int x = p[i] / 2;
int l = i / 2 - x - 1;
int r = i / 2 + x;
res = min(res, dl[l] + suf[r]);
res = min(res, pre[l] + dr[r]);
}
else
{
int x = p[i] / 2;
int l = i / 2 - x;
int r = i / 2 + x;
res = min(res, dl[l - 1] + suf[r + 1]);
res = min(res, pre[l - 1] + dr[r + 1]);
}
}
printf("%lld\n", res);
return 0;
}