NC17062 回文

链接:回文 (nowcoder.com)

思路:

因为是回文子串,所以肯定枚举中间切割点,当前中间切割点的最长回文子串是保留的。

比如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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值