D. Prefix-Suffix Palindrome (Manacher)

21 篇文章 1 订阅
6 篇文章 0 订阅

D1. Prefix-Suffix Palindrome (Easy version)

题意: 给一个字符串 s,求一个字符串 t,t 由 s 的某个前缀以及某个后缀拼接而成,且 t 是回文串,长度不能超过 s。输出最长的 t
思路: 可以证明 :当 s = “ a b c x x c b a w u v c b a ” s=“abcxxcbawuvcba” s=abcxxcbawuvcba,最终答案一定包含首部的 a b c abc abc 以及尾部的 c b a cba cba 两部分。因为此时的最大回文串有两种选法:1、全选前缀 a b c x x c b a abcxxcba abcxxcba ;2、选前缀 a b c x x abcxx abcxx,与后缀 c b a cba cba。两种选法得到的答案是相同的,所以我们干脆就都选首部的 a b c abc abc与尾部的 c b a cba cba,然后在剩下的中间的字符串中找个最长的前后缀的回文子串,选较长的作为答案即可;
然后easy版的显然直接暴力搞就好了;

#include<bits/stdc++.h>

using namespace std;

string s;

bool check(int l,int r){	//判断是否为回文
	while(l<=r) {
		if(s[l]==s[r]) l++,r--;
		else return false;
	}
	return true;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int T;
	cin>>T;
	while(T--){
		cin>>s;
		string ans;
		int i=0,j=s.size()-1;
		while(i<=j){	//先找到首尾回文的最大位置,截取中间的字符串
			if(s[i]==s[j]) i++,j--;
			else break;
		}
		if(i>j) {	//整个串是回文的
			cout<<s<<"\n";continue;
		}
		int l=i,r=j;	//前缀最大回文子串
		while(r>=i){
			if(check(i,r)) break;
			else r--;
		}
		while(l<=j){	//后缀最大回文子串
			if(check(l,j)) break;
			else l++;
		}
		string ls = "",rs = "";
		if(i!=0){	//防止首尾不回文
			ls = s.substr(0,i);
			rs = ls;
			reverse(rs.begin(),rs.end());
		}
		if(r-i>=j-l) ans = ls + s.substr(i,r-i+1) + rs;		//二者取最大拼接
		else ans = ls + s.substr(l,j-l+1) + rs;
		cout<<ans<<"\n";
	}
	return 0;
}

D2. Prefix-Suffix Palindrome (Hard version)

然后数据范围变大后,我们可以利用马拉车算法优化寻找最长的子串的时间,其他的代码不变;

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;

namespace Manacher{
    vector<int> ans, str, lef;
    int build(const string &s){
        // 初始化
        int n = s.length(), m = (n + 1) << 1, ret = 0;
        str.resize(m + 1); ans.resize(m + 1); lef.resize(m + 1);
        // $ - 串开始标记, @ - 串结束标记,# - 将字母间隔开。注意这里的字符必须是字符串内不存在的
        str[0] = '$'; str[m] = '@'; str[1] = '#'; ans[1] = 1;
        for (int i = 1; i <= n; i++) 
            str[i << 1] = s[i - 1], str[i << 1 | 1] = '#';
        // Manacher 操作。r, p 分别维护当前最靠右最长回文串的右边界与中心
        ans[1] = 1;
        for (int r = 0, p = 0, i = 2; i < m; ++i){
            if (r > i) ans[i] = min(r - i, ans[p * 2 - i]); else ans[i] = 1; // Manacher 核心操作
            while(str[i - ans[i]] == str[i + ans[i]]) ++ans[i]; // 暴力向外扩展
            if (i + ans[i] > r) r = i + ans[i], p = i; // 尝试更新 r, p
            ret = max(ret, ans[i] - 1); // 更新答案
        }
        // 计算维护以每个位置为起点的最长回文串
        for (int i = 0; i <= m; i++) lef[i] = 0;
        for (int i = 2; i < m; i++) if (lef[i - ans[i] + 1] < i + 1) lef[i - ans[i] + 1] = i + 1;
        for (int i = 1; i <= m; i++) if (lef[i] < lef[i - 1]) lef[i] = lef[i - 1];
        return ret;
    }
    int mid(int x, bool odd){	//返回以x为中心的最长回文子串的长度,odd为x的奇偶
        if (odd) return ans[(x + 1) << 1] - 1;
        return ans[(x + 1) << 1 | 1] - 1;
    }
    int left(int x){ return lef[(x + 1) << 1] - ((x+1) << 1); }	//返回原串中以x为起点的最长回文子串长度
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int T;
	cin>>T;
	while(T--){
		string s;
		cin>>s;
		int i=0,j=s.size()-1;
		while(i<=j){
			if(s[i]==s[j]) i++,j--;
			else break;
		}
		if(i>j) {
			cout<<s<<"\n";continue;
		}
		string ls = "",rs = "";
		if(i!=0){
			ls = s.substr(0,i);
			rs = ls;
			reverse(rs.begin(),rs.end());
		}
		string t = s.substr(i,j-i+1);	//截取中间串
		Manacher::build(t);
		int l = Manacher::left(0);  //前缀最长长度
		reverse(t.begin(),t.end());
		Manacher::build(t);
		int r = Manacher::left(0);  //后缀最长长度
		string ans;
		if(l>=r) ans = ls + s.substr(i,l) + rs;
		else ans = ls + s.substr(j-r+1,r) + rs;
		cout<<ans<<"\n";
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值