链接:https://www.nowcoder.com/acm/contest/131/D
来源:牛客网
题目描述
字符串 S 只包含小写英文字母。有四种操作,每次操作你可以选择其中一种:
删除字符串的第一个字母。
删除字符串的最后一个字母。
在字符串的头部添加任意一个你想要的字母。
在字符串的尾部添加任意一个你想要的字母。
删除一个第 i 种英文字母需要的花费是 Ai,添加一个第 i 种英文字母的花费是 Bi。
请问将字符串 S 变成回文串需要的最小花费是多少?
输入描述:
第一行输入一个字符串 S。 接下来 26 行,每行输入两个正整数 Ai 和 Bi,表示删除一个第 i 种字符所需的花费以及添加一个第 i 种字符所需的花费。 1≤ |S| ≤ 105 且字符串 S 中只包含小写英文字母. 1≤ Ai,Bi≤ 109.
输出描述:
输出一个正整数,表示把字符串 S 变成一个回文串的最小花费。
题解:
答案一定是保留某个回文,然后一边保留一部分,另一边变成这部分。回文树找i位置最长回文,前缀预处理一下即可。
代码:
#include <bits/stdc++.h>
#ifdef LOCAL
#define debug(x) cout<<#x<<" = "<<(x)<<endl;
#else
#define debug(x) 1;
#endif
#define chmax(x,y) x=max(x,y)
#define chmin(x,y) x=min(x,y)
#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
#define fir first
#define sec second
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int MOD = 1e9 + 7;
const double PI = acos (-1.);
const double eps = 1e-10;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 5e5 + 5;
const int N = 26 ;
struct Palindromic_Tree {
int nxt[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点
int cnt[MAXN] ; //i表示的回文串在原串出现多少次(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的)
int num[MAXN] ; //表示以节点i表示的最长回文串的最右端点为回文串结尾的回文串个数
int len[MAXN] ;//len[i]表示节点i表示的回文串的长度(一个节点表示一个回文串)
int S[MAXN] ;//存放添加的字符
int last ;//指向新添加一个字母后所形成的最长回文串表示的节点。
int n ;//表示添加的字符个数。
int p ;//表示添加的节点个数。
int newnode ( int l ) {//新建节点
for ( int i = 0 ; i < N ; ++ i ) nxt[p][i] = 0 ;
cnt[p] = 0 ;
num[p] = 0 ;
len[p] = l ;
return p ++ ;
}
void init () {//初始化
p = 0 ;
newnode ( 0 ) ;
newnode ( -1 ) ;
last = 0 ;
n = 0 ;
S[n] = -1 ;//开头放一个字符集中没有的字符,减少特判
fail[0] = 1 ;
}
int get_fail ( int x ) {//和KMP一样,失配后找一个尽量最长的
while ( S[n - len[x] - 1] != S[n] ) x = fail[x] ;
return x ;
}
int add ( int c ) {
S[++ n] = c ;
int cur = get_fail ( last ) ;//通过上一个回文串找这个回文串的匹配位置
if ( !nxt[cur][c] ) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
int now = newnode ( len[cur] + 2 ) ;//新建节点
fail[now] = nxt[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转
nxt[cur][c] = now ;
num[now] = num[fail[now]] + 1 ;
}
last = nxt[cur][c] ;
cnt[last] ++ ;
return len[last];
}
void count () {
for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ;
//父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!
}
} pam;
char s[MAXN];
int cost1[MAXN], cost2[MAXN];
ll pre1[MAXN], pre2[MAXN];
ll minn[MAXN];
ll solve (int n) {
minn[n + 1] = INFLL;
for (int i = 1; i <= n; i++) {
pre1[i] = pre1[i - 1] + cost1[s[i] - 'a'];
pre2[i] = pre2[i - 1] + cost2[s[i] - 'a'];
}
for (int i = n; i >= 1; i--) minn[i] = min(minn[i + 1], pre2[i] - pre1[i]);
pam.init();
ll ret = INFLL;
for (int i = 1; i <= n; i++) {
int len = pam.add(s[i] - 'a');
ret = min(ret, pre1[i - len] + minn[i] + pre1[n] - pre2[i]);
}
return ret;
}
int main() {
#ifdef LOCAL
freopen ("input.txt", "r", stdin);
#endif
scanf ("%s", s + 1);
int n = strlen(s + 1);
for (int i = 0; i < 26; i++) scanf ("%d %d", &cost1[i], &cost2[i]);
ll ans = INFLL;
chmin(ans, solve(n));
reverse(s + 1, s + 1 + n);
chmin(ans, solve(n));
printf ("%lld\n", ans);
return 0;
}