ural -1297. Palindrome----后缀数组解决最长回文串的问题

此题就是问一个只含有字母的的字符串中的最长的回文串是什么。如果有多解的话,打印出最先出现的那个。

首先考虑到回文串的特点。那么我们的肯定想到将字符串反转,并加到原字符串后面,用一个字符隔开。然后就是求后缀的最长前缀。

这里最长前缀是我们已知道的算法。这里我们不适用RMQ来解。只要一个O(n)的时间就可以解决了。遍历一下height数组就行了。

具体见代码。

//
//  main.cpp
//  ura 1297. Palindrome -----后缀数组的妙用
//
//  Created by XD on 15/9/5.
//  Copyright (c) 2015年 XD. All rights reserved.
//

#include <iostream>
#include <string>
#include <queue>
#include <stack>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include<vector>
#include <string.h>
#include <string>
#include <algorithm>
#include <set>
#include <map>
#include <cstdio>
using namespace std ;
const int maxn = 3000 + 10 ;
int s[maxn] ;
int t[maxn],t1[maxn] , r[maxn] ,height[maxn],c[maxn] ,sa[maxn];
int min(int x , int y )
{
    return x >y ?y:x ;
}
int max(int x, int y )
{
    return x > y ?x:y ;
}
void build_sa(int n ,int m)
{
    int *x =t ,*y=t1 ,*temp ;
    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>-1;i--) sa[--c[x[i]]] = i ;
    for(int k = 1; k <= n ;k<<=1)
    {
        int p = 0 ;
        for(int i= n-k ; i < n ;i++ ) y[p++] = i ;
        for(int i= 0 ;i < n ;i++) if(sa[i] >= k ) y[p++] = sa[i]- k ;
        for(int i = 0 ; i < m ; i++) c[i] = 0;
        for(int i =0 ; i < n ; i ++) c[x[y[i]]]++ ;
        for(int i = 1 ; i < m ; i++) c[i]+=c[i-1] ;
        for(int i = n -1; i > -1;i--) sa[--c[x[y[i]]]] = y[i] ;
        temp = x ; x =y ; y =temp ;
        p = 1 ; x[sa[0]] = 0 ;
        for(int i = 1 ; i < n ; i++)
        {
            x[sa[i]] = (y[sa[i]] == y[sa[i-1]] && y[sa[i] + k ] == y[sa[i-1] + k ])?p-1:p++ ;
        }
        if (p >= n ) {
            break ;
        }
        m = p;
    }
}
void getHeight(int n )
{
    for(int i = 0 ; i < n ; i ++)
    {
        r[sa[i]] = i ;
    }
    int k = 0;
    for (int i = 0 ; i <n-1 ; i++) {
        if (k) {
            k-- ;
        }
        int j = sa[r[i]-1] ;
        while (s[i+k] == s[j+k]) {
            k++ ;
        }
        height[r[i]] = k ;
    }
}

int main() {
    char t[maxn] ;
    gets(t) ;
    int len=(int) strlen(t) ;
    for (int i = 0 ; i < len; i++) {
        s[i] = t[i] ;
    }
    if (len == 0 ) {
        printf("\n") ;
        return 0 ;
    }
    s[len] = 1 ;
    for (int i = 0 ; i < len; i++) {
        s[len+1 + i ] = t[len-1-i] ;
    }
    s[2 * len+1] = 0 ;
    build_sa(2*len+2, 130) ;//构建后缀数组
    getHeight(2*len +2) ;//获取height数组
    //下面为求最长的前缀。
    /*对于按照字典序排列的一系列后缀,其中第i个后缀,与这个后缀具有最长前缀的后缀肯定是相邻的后缀(也就是rank[i] +- 1的后缀.
         上面的结论可以使用反正法证明。假设存在这样的后缀t使得rank[t] > rank[i] + 1 且小于rank[i]-1那么对于rank[t] 和rank[i]之间的后缀则会出现矛盾。用后缀大小关系就可推出矛盾。
     */
    
    
    int start = 0 , ans =1;
    for (int i = 3; i <2*len+2; i++) {
        int t1 = min(sa[i],sa[i-1]) ;int t2 = max(sa[i],sa[i-1]) ;
        //这个if的条件十分重要,首先两个后缀得 一个来自原串,一个来自反转之后的串。另外两个后缀起始位置之间的关系也十分重要。这个是确定这两个后缀的前缀在原串时同一个区段的串。
        if (t1 < len &&t2 >len
            && t2-t1==len -t1+ len - (t1 + height[i]-1)) {
            if(height[i] == ans )
            {
                start = min(start,min(sa[i],sa[i-1])) ;
            }
            else if (height[i] > ans)
            {
                start = min(sa[i],sa[i-1]) ;
                ans  = height[i] ;
            }
        }
    }
    for (int i = start; i-start < ans; i++) {
        printf("%c" , s[i]) ;
    }
    printf("\n") ;
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值