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