【前缀和 && (扩展KMP求前缀和后缀回文串长度 || manacher)】HDU - 3613 Best Reward

Step1 Problem:

给你 ‘a’ 到 ‘z’ 字符对应的价值,让你求把字符串分为两份后,对于每一份如果是回文串,就获取其价值,否则为0.
求讲字符串分成两份后的最大价值。

Step2 Ideas:

枚举分割点,O(1)判断分割后的前缀和后缀是否是回文串,是的话O(1)求出价值。取最大。
O(1) 判断是否是回文,可以用扩展KMP 或者 manacher 先处理好
O(1) 求出价值,可以用前缀和预处理一下。
扩展KMP:
next[i]:i 位置开始的后缀串原串最长公共前缀长度。
extend[i]: i 位置开始的后缀串另一个串(也就是求next[]的串)最长公共前缀长度。
求所有是回文串的前缀 原串(求原串的next[])去匹配反串(原串翻转后的串)
求所有是回文串的后缀 反串(求反串的next[])去匹配原串
manacher:
字符串处理后的长度为 len-1,p[i]-1 代表以 i 为中心的回文串的长度。

Step3 Code:

//扩展KMP做法
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+100;
char s[N], fzs[N];
int nex[N], extend[N];
int p[N], sum[N], v[N];
bool qz[N], hz[N];
void get_next(char s[])
{
    int len = strlen(s);
    int mx = 0, id;
    nex[0] = len;
    for(int i = 1; i < len; i++)
    {
        if(i < mx) nex[i] = min(mx-i, nex[i-id]);
        else nex[i] = 0;
        while(s[nex[i]+i] == s[nex[i]]) nex[i]++;
        if(mx < i+nex[i])
        {
            id = i;
            mx = i+nex[i];
        }
    }
}
void get_extend(char s1[], char s2[])
{
    int mx = 0, id;
    int len = strlen(s1);
    for(int i = 0; i < len; i++)
    {
        if(i < mx) extend[i] = min(mx-i, nex[i-id]);
        else extend[i] = 0;
        while(s1[i+extend[i]] == s2[extend[i]]  && s2[extend[i]] != '\0') extend[i]++;
        if(mx < i+extend[i])
        {
            id = i;
            mx = i+extend[i];
        }
    }
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        for(int i = 0; i < 26; i++)
            scanf("%d", &v[i]);
        scanf("%s", s);
        int len = strlen(s);
        for(int i = 0; i < len; i++)//求反串
            fzs[len-i-1] = s[i];
        fzs[len] = '\0';
        memset(qz, 0, sizeof(qz));
        memset(hz, 0, sizeof(hz));

        get_next(s);//原串匹配反串求前缀回文
        get_extend(fzs, s);
        for(int i = 0; i < len; i++)
            if(i+extend[i] == len) qz[extend[i]] = 1;//满足条件是回文串
        get_next(fzs);//反串匹配原串求后缀回文
        get_extend(s, fzs);
        for(int i = 0; i < len; i++)
            if(i+extend[i] == len) hz[extend[i]] = 1;//满足条件是回文串

        sum[0] = 0;
        for(int i = 1; i <= len; i++)//预处理前缀和 注意这里是从1开始
            sum[i] = sum[i-1] + v[s[i-1]-'a'];

        int ans = 0;
        for(int i = 1; i < len-1; i++)//枚举分割后前缀的长度
        {
            int tmp = 0;
            if(qz[i] == 1) tmp += sum[i];//如果前缀是回文
            if(hz[len-i] == 1) tmp += sum[len] - sum[i];//如果后缀是回文
            ans = max(ans, tmp);
        }
        printf("%d\n", ans);
    }
    return 0;
}
//manacher做法
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+100;
char sx[N], s[N];
int p[N], sum[N], v[N];
int get_s()
{
    int j = 0;
    s[j++] = '@'; s[j++] = '#';
    int len = strlen(sx);
    for(int i = 0; i < len; i++)
    {
        s[j++] = sx[i];
        s[j++] = '#';
    }
    s[j] = '\0';
    return j;
}
void manacher(int len)
{
    int mx = 0, id;
    for(int i = 1; i < len; i++)
    {
        if(i < mx) {//这时可直接获得p[i],时间优化
            p[i] = min(p[2*id-i], mx - i);
        }
        else p[i] = 1;
        while(s[i-p[i]] == s[i+p[i]]) p[i]++;
        if(i+p[i] > mx) {
            id = i;
            mx = i+p[i];
        }
    }
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        for(int i = 0; i < 26; i++)
            scanf("%d", &v[i]);
        scanf("%s", sx);
        int len = get_s();
        manacher(len);//求出p[i]数组
        sum[0] = 0;
        for(int i = 1; i < len; i++)//预处理前缀和
        {
            sum[i] = sum[i-1];
            sum[i] += (s[i]=='#') ? 0:v[s[i]-'a'];
        }
        int ans = 0;
        for(int i = 3; i <= len-3; i = i+2)//枚举所有的'#'
        {
            int lpos = (1+i)/2;//前缀的中点
            int rpos = (i+len-1)/2;//后缀的中点
            int tmp = 0;
            if(p[lpos]-1 == (i-1)/2) tmp += sum[i];//前缀如果是回文
            if(p[rpos]-1 == (len-1-i)/2) tmp += sum[len-1] - sum[i-1];//后缀如果是回文
            ans = max(ans, tmp);
        }
        printf("%d\n", ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/bbbbswbq/article/details/80749781
今日推荐