HDU 3613 Best Reward(KMP+思维||manacher算法)

15 篇文章 0 订阅
8 篇文章 0 订阅

题目链接:点击打开链接

题意:26个小写英文字母都有一个价值,给你一个字符串,将该字符串切成左右两半,对于每一半,如果是回文串,就获得该子串的字母价值之和,否则该子串的价值为0。求出将字符串切成两半后能够获得的最大价值。

思路一:KMP+思维。

思路二:manacher算法(马拉车算法)

具体思路明天写......

咳咳,回来写思路了......

主要介绍KMP+思维实现,基于manacher算法的方法和代码略。

思路:

首先要熟悉回文串的基本特征,正反读,字符串是一样的!!!题目说左右两半都是回文串,那么可以考虑把字符串前缀和后缀里所有的回文串找到,怎么找呢?先拿前缀考虑,如果把串反转,那么前缀的回文序列,在反转串的后缀里一定出现,利用KMP算法匹配原串前缀和反转串后缀,一定会匹配出前缀的最长回文序列,同理,匹配反转串前缀和原串后缀,可以匹配出原串的最长回文后缀。得到的是最长的回文串,可是我们需要标记出所有的呀,咋办?

思维处理出现了,还是拿前缀为例,已经匹配出的最长回文前缀设为s,如果s里面还存在一个最长的回文前缀t,t的长度肯定是小于s,而且,由于s回文,那么从s末尾向前读,读t的长度个字符,依次读取到的序列和t一定相同,这段序列记为tt,由于t回文,t和tt相同,那么tt是回文串,所以t和tt匹配,由此可以联想到Next函数,那么Next[s的长度数值]会是t的长度数值吗?可以利用反证法,假设利用Next函数得到的长度大于此长度,序列记做u,即u不是回文串,与此匹配的后缀v肯定是与u相同,由于s是回文串,v还要反向和u一致,所以v是回文串,与u不是回文串矛盾,

所以由s的Next得到的位置即为下一个最长回文前缀的位置,以此类推,得到所有的回文前缀。后缀思路一致,分析略。

分析到这,剩下的就很简单了,枚举分割点,计算左右价值总和,更新最大值。

// HDU 3613 Best Reward 运行/限制:93ms/1000ms
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
char s[500005],p[500005];//原串 反向串
int Next[500005];
int v[26],sum[500005];//字母对应的权值 原串权值前缀和
int pre[500005], pos[500005];//记录前缀为回文串的情况 记录后缀为回文串的情况(位置标号为反向串下标)
void getNext(char s[], int n) {
	int i, j;
	i = -1, j = 0;
	Next[0] = -1;
	while (j < n) {
		if (i == -1 || s[i] == s[j]) {
			i++; j++;
			Next[j] = i;//不优化
		}
		else {
			i = Next[i];
		}
	}
}
int kmp(char s[], char p[], int n) {
	int i, j;
	i = j = 0;
	getNext(s, n);
	while (j < n) {
		if (i == -1 || p[j] == s[i]) {
			i++; j++;
		}
		else {
			i = Next[i];
		}
	}
	return i;
}
int main(){
	int t, len, k, re, ans;
	scanf("%d", &t);
	while (t--) {
		re = -INF;
		for (int i = 0; i < 26; i++) {
			scanf("%d", &v[i]);
		}
		scanf("%s", s);
		len = strlen(s);
		memset(pre, 0, sizeof(pre));
		memset(pos, 0, sizeof(pos));
		p[0] = s[len - 1];
		sum[0] = v[s[0] - 'a'];
		for (int i = 1; i < len; i++) {
			p[i] = s[len - i - 1];
			sum[i] = sum[i - 1] + v[s[i] - 'a'];
		}
		p[len] = '\0';
		k = kmp(s, p, len);
		while (k > 0) {//记录前缀回文串
			pre[k - 1] = 1;
			k = Next[k];
		}
		k = kmp(p, s, len);
		while (k > 0) {//记录后缀回文串
			pos[k - 1] = 1;
			k = Next[k];
		}
		for (int i = 0; i < len - 1; i++) {//枚举前半部分终点
			ans = 0;
			if (pre[i]) ans += sum[i];
			if (pos[len - 2 - i]) ans += sum[len - 1] - sum[i];
			re = max(re, ans);
		}
		printf("%d\n", re);
	}
    return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值