ural 1297 最长回文子串

本文介绍了一种使用后缀数组高效求解字符串中最长回文子串的方法。通过对原始字符串进行特殊处理并利用后缀数组的特性,将问题转化为求解最长公共前缀的问题,并通过RMQ算法进一步优化查询过程。

参考:基本是看他的http://bbezxcy.iteye.com/blog/1418354

参考论文《后缀数组--处理字符串的有力工具》

题目大意:要你求出一个字符串的最长回文子串

解题思路:常规方法是枚举每个字符,每个字符两边扩展,比如第i个字符,分奇偶两种

str[m] .... str[i] ..... str[n] m到i的字符串与n到i的字符串匹配

str[m]......str[i-1]str[i].....str[n],m到i-1的字符串与n到i的字符串匹配这两种

算法复杂度为O(n^2)

转为利用后缀数组,把整个字符串反向接在字符串后面,处理后的长度为原来的两倍,2*len

当以i为轴的回文子串,i为原字符串的下标,当长度为奇数回文子串时,i与2*len - i - 1匹配

当为偶数回文子串时,i与2 * len - i匹配

这样就转换成为了求新字符串的两个后缀的最长公共前缀,为了避免后面拼接的反向串成为公共前缀,用一个未出现的字符串把原串和反向串隔开

然后利用后缀数组中的height和rank的特性,两个后缀的起始位置为i,j,i < j, 那么最长公共前缀为height[rank[i]+1],height[rank[i]+2].....height[rank[j]的最小值,可以转换用RMQ来求区间最小

下面的best[i][j]表示从第j个位置到j+ 2 ^i个位置的最小值

#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

const int maxn = 2010;

char str[maxn], tstr[maxn];
int height[maxn], sa[maxn], r[maxn], wx[maxn], wy[maxn], c[maxn];
int Log[maxn];
int best[20][maxn];

inline bool cmp(int *s, int a, int b, int l);
void suffix(char *s, int n, int m);
void cal_height(char *s, int *sa, int n);
void initRMQ(int n);
int lcp(int a,int b);

int main()
{
    
    int n;
    Log[0] = -1;
    for(int i = 1; i < maxn; i++)
        Log[i] = (i & (i-1)) ? Log[i-1] : Log[i-1] + 1 ; //i是否是2的幂
    
    scanf("%s", str);
    int len = strlen(str);
    for(int i = 0; i < len; i++)
        str[len + i + 1] = str[len - i - 1];
    str[len] = 126;
    n = 2 * len + 1;
    str[n] = 0;
    suffix(str, n + 1, 128);
    cal_height(str, sa, n);
    initRMQ(n);
    int start, ans = 1,tmp;
    for(int i = 0; i < len; i++)
    {
        tmp = lcp(i,n-i-1);    ///首先考虑回文长度是奇数的情况
        if(tmp * 2 - 1 > ans)
        {
            ans = tmp * 2 - 1;
            start = i - tmp + 1;
        }
        tmp = lcp(i, n - i);    ///考虑回文长度是偶数的情况
        if(tmp * 2 > ans)
        {
            ans = tmp * 2;
            start = i - tmp;
        }
    }
    if(ans == 1)
        printf("%c\n",str[0]);
    else
    {
        for(int i = start; i < start + ans; i++)
            printf("%c", str[i]);
        printf("\n");
    }
    
    return 0;
}

inline bool cmp(int *s, int a, int b, int l)
{
    return s[a] == s[b] && s[a + l] == s[b + l];
}


void suffix(char *s, int n, int m)
{
    int *x = wx, *y = wy, *t, index;
    for(int i = 0; i < m; i++)
        c[i] = 0;
    for(int i = 0; i < n; i++)
        c[x[i] = s[i]]++;
    for(int i = 1; i < m; i++)
        c[i] += c[i - 1];
        
    for(int i = n - 1; i >= 0; i--)
        sa[--c[s[i]]] = i;
        
    for(int j = 1; j < n; j *= 2, m = index)
    {
        index = 0;
        for(int i = n - j; i < n; i++)
            y[index++] = i;
        for(int i = 0; i < n; i++)
        {
            if(sa[i] >= j)
                y[index++] = sa[i] - j;
        }
        for(int i = 0; i < m; i++)
            c[i] = 0;
            
        for(int i = 0; i < index; i++)
            c[x[y[i]]]++;
        for(int i = 1; i < m; i++)
            c[i] += c[i - 1];
            
        for(int i = index - 1; i >= 0; i--)
            sa[--c[x[y[i]]]] = y[i];
            
        t = x; x = y; y = t;
        x[sa[0]] = r[sa[0]] = 0;
        index = 1;
        for(int i = 1; i < n; i++)
        {
            if(j * 2 < n)
                x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? index - 1 : index++;
            else
                r[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? index - 1 : index++;
        }
    }
}



void cal_height(char *s, int *sa, int n)
{
    int k = 0;
    for(int i = 0; i < n; height[r[i++]] = k)
    {
        if(k != 0)
            k--;
        for(int j = sa[r[i] - 1]; s[i + k] == s[j + k]; k++);
    }
}


void initRMQ(int n)
{//初始化RMQ
    for(int i = 1; i <= n ; i ++)
        best[0][i] = height[i];
    for(int i = 1; i <= Log[n] ; i++)
    {
        int limit = n - (1 << i) + 1;
        for(int j = 1; j <= limit; j ++)
            best[i][j] = min(best[i-1][j] , best[i-1][j+(1 << i >> 1)]);
    }
}

int lcp(int a,int b)
{//询问a,b后缀的最长公共前缀
    a = r[a];
    b = r[b];
    if(a > b)
        swap(a,b);
    a++;
    int t = Log[b - a + 1];
    return min(best[t][a] , best[t][b - (1 << t) + 1]);
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值