UVA11475 Extend to Palindrome(扩展KMP/Manacher/后缀数组)

5 篇文章 0 订阅
5 篇文章 0 订阅

题意:给一个长度最大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");
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值