题意:给一个长度最大100000的字母串,可在它后面任意补充字符,输出可以构成的最短回文串。
思路:补充的部分要与前面形成回文,要求出来最少的情况,显然我们要先求出原串的后缀能够形成的最大回文串,然后补充剩下的前缀部分的对称串即可。最好的情况就是原串回文,不需要补充,最坏的情况是以最后一个字母为对称中心,要补充n-1个字母。
求最大的回文后缀,有几种方法:
Manacher:
在串上跑一次Manacher,然后遍历结果p数组,如果某个点的回文半径达到了串的末尾,就是一个回文后缀,从头开始遍历找到的第一个就是最大的回文后缀。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int maxn = 200005;
int pos, maxlen, st;
int manacher(char s[], char str[], int p[]){
int len = strlen(s);
str[0] = '*';
for(int i = 0; i <= len; ++i){
str[i * 2 + 1] = '#';
str[i * 2 + 2] = s[i];
}
len = 2 * len + 1;
pos = 0, maxlen = 1;
p[0] = 1;
for(int i = 2; i < len; ++i){
if(i < p[pos] + pos){
p[i] = min(pos + p[pos] - i, p[pos * 2 - i]);
} else {
p[i] = 1;
}
while(str[i - p[i]] == str[i + p[i]]){
++p[i];
}
if(i + p[i] > pos + p[pos]){
pos = i;
}
if(p[i] > maxlen){
maxlen = p[i];
st = i;
}
}
return maxlen - 1;
}
char s[maxn], str[maxn];
int p[maxn];
int main(){
while(~scanf("%s", s)){
int l1 = strlen(s);
manacher(s, str, p);
int len = strlen(str);
int ans, r;
for (int i = 2; i < len - 1; ++i) {
if (p[i] + i >= len) {
ans = i / 2 - 1;
r = p[i] / 2;
break;
}
}
printf("%s", s);
for (int i = ans - r; ~i; --i) {
printf("%c", s[i]);
}
printf("\n");
}
return 0;
}
/*
aaaa
abba
amanaplanacanal
xyz
*/
扩展KMP:
因为要找的后缀是回文的,我们将原串倒转设为模式串,与原串跑一次扩展KMP求出模式串的前缀与原串的每一位的最大匹配长度。如果结果中原串的某个后缀与模式串前缀匹配的长度等于后缀长度,那么就是一个回文后缀,找到最大的一个即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int maxn = 200005;
int extend[maxn], nxt[maxn];
void get_next(char str[]){
int i = 0, j, pos;
int len = strlen(str);
nxt[0] = len;
while(i + 1 < len && str[i] == str[i + 1]){
++i;
}
nxt[1] = i;
pos = 1;
for(i=2; i<len; ++i){
if(nxt[i - pos] < pos + nxt[pos] - i){
nxt[i] = nxt[i - pos];
} else {
j = nxt[pos] + pos - i;
if(j < 0){
j = 0;
}
while(i + j < len && str[i + j] == str[j]){
++j;
}
nxt[i] = j;
pos = i;
}
}
}
void EXKMP(char target[], char pattern[]){
int i = 0, j, pos;
get_next(pattern);
int len1 = strlen(target);
int len2 = strlen(pattern);
while(i < len1 && i < len2 && target[i] == pattern[i]){
++i;
}
extend[0] = i;
pos = 0;
for(i=1; i<len1; ++i){
if(nxt[i - pos] < pos + extend[pos] - i){
extend[i] = nxt[i - pos];
} else {
j = extend[pos] + pos - i;
if(j < 0){
j = 0;//从头匹配
}
while(i + j < len1 && j < len2 && target[i + j] == pattern[j]){
++j;
}
extend[i] = j;
pos = i;//更新pos
}
}
}
char s1[maxn], s2[maxn];
int main(){
while (~scanf("%s", s1)) {
int len = strlen(s1);
for (int i = 0; s1[i]; ++i) {
s2[len - i - 1] = s1[i];
}
s2[len] = 0;
EXKMP(s1, s2);
int ans, r;
for (int i = 0; i < len; ++i) {
if (extend[i] + i >= len) {
ans = i - 1;
break;
}
}
printf("%s", s1);
for (int i = ans; i >= 0; --i) {
printf("%c", s1[i]);
}
printf("\n");
}
return 0;
}
后缀数组:
将原串反转接到原串后面,中间用'#'分隔,然后跑一遍SA。二分回文后缀的长度,判断时将height按照当前二分的长度x分组,如果一组中同时含有原串的后缀和倒转串的前缀即为真。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <stack>
#include <cmath>
#include <list>
#include <cstdlib>
#include <set>
#include <map>
#include <vector>
#include <string>
using namespace std;
typedef long long ll;
const ll linf = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int maxn = 200005;
const int mod = 1000000007;
char s[maxn];
int wa[maxn],wb[maxn],wv[maxn],Ws[maxn];
int cmp(int *r,int a,int b,int l)
{return r[a]==r[b]&&r[a+l]==r[b+l];}
// 传入参数:str,sa,len+1,ASCII_MAX+1
void da(const char r[],int sa[],int n,int m) {
int i,j,p,*x=wa,*y=wb,*t;
for(i=0; i<m; i++) Ws[i]=0;
for(i=0; i<n; i++) Ws[x[i]=r[i]]++;//以字符的ascii码为下标
for(i=1; i<m; i++) Ws[i]+=Ws[i-1];
for(i=n-1; i>=0; i--) sa[--Ws[x[i]]]=i;
//for(int i=0;i<n+1;i++)cout<<sa[i]<<' ';
for(j=1,p=1; p<n; j*=2,m=p) {
for(p=0,i=n-j; i<n; i++) y[p++]=i;
for(i=0; i<n; i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0; i<n; i++) wv[i]=x[y[i]];
for(i=0; i<m; i++) Ws[i]=0;
for(i=0; i<n; i++) Ws[wv[i]]++;
for(i=1; i<m; i++) Ws[i]+=Ws[i-1];
for(i=n-1; i>=0; i--) sa[--Ws[wv[i]]]=y[i];
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1; i<n; i++)
x[sa[i]]=(y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j])?p-1:p++;
//x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
int sa[maxn],rk[maxn],height[maxn];
// sa,rk,height 下标均从1开始,串下标从1开始计数
// str,sa,len
void calheight(const char *r,int *sa,int n) {
int i,j,k=0;
for(i=1; i<=n; i++) rk[sa[i]]=i;
for(i=0; i<n; height[rk[i++]]=k)
for(k?k--:0,j=sa[rk[i]-1]; r[i+k]==r[j+k]; k++);
for(int i=n;i>=1;--i) ++sa[i],rk[i]=rk[i-1];
}
bool jud(int x, int l1, int n) {
int f1 = 0, f2 = 0;
for (int i = 2; i <= n; ++i) {
if (height[i] >= x) {
if (sa[i - 1] <= l1 && sa[i - 1] + height[i] >= l1) {
f1 = 1;
}
if (sa[i] <= l1 && sa[i] + height[i] >= l1) {
f1 = 1;
}
if (sa[i - 1] == l1 + 2 || sa[i] == l1 + 2) {
f2 = 1;
}
if (f1 && f2) return true;
} else {
f1 = 0, f2 = 0;
}
}
return false;
}
int main() {
while (~scanf("%s", s)) {
int len = strlen(s);
int l1 = len;
s[len] = '#';
for (int i = 0; i < len; ++i) {
s[len + i + 1] = s[len - 1 - i];
}
len = len * 2 + 1;
s[len] = 0;
da(s, sa, len + 1, 130);
calheight(s, sa, len);
int l = 1, r = l1 + 1, mid;
while (l < r - 1) {
mid = (l + r) >> 1;
if (jud(mid, l1, len)) {
l = mid;
} else {
r = mid;
}
}
int ans = l1 - l - 1;
for (int i = 0; i <= ans; ++i) {
printf("%c", s[i]);
}
for (int i = l1 - 1; ~i; --i) {
printf("%c", s[i]);
}
printf("\n");
}
}