zoukankan      html  css  js  c++  java
  • 题解-最长回文子串


    刚学完后缀数组,用这道题来练练(SA)

    (〇)题目描述

    题目右转:URAL 1297

    题意如题目,即给出一个字符串 (S) ,求 (S) 的最长回文子串。((|S|leqslant1000))

    (二)解题思路

    既然刚学了后缀数组,自然使用后缀数组做啦。

    但如何将问题转化为一个 (SA) 问题呢(?)

    先来观察一下下面这个字符串的回文子串吧。(RT,)

    可以发现,回文子串 (baaaab),是由两个部分组成(前半部分和后半部分)。前半部分是字符串 (aabaaaab) 的前缀的后缀,后半部分是原字符串的后缀的前缀,回文子串即为原字符串的前缀的后缀和原字符串的后缀的前缀两部分组成(可能读起来有点绕,需要认真理解)。

    回文串有个重要的性质,即从字符串中心将字符串切开,后半部分刚好是反过来的前半部分(例如: (abc) 是反过来的 (cba) )。因此,若一个子串是回文的,那么其一定是由原字符串某个后缀的前缀和原字符串的某个前缀的后缀的公共部分组成的。

    如果我们将字符串调转过来,在接到原字符串的后面,中间隔一个字典序最小的'板',这样前缀就会被调转过来,‘前缀的后缀’就会变为‘后缀的前缀’。问题就转化为了求某两个后缀的最长公共前缀,就可以用后缀数组求解了。

    (三)解题方法

    后缀数组+(RMQ)

    时间复杂度:(Theta(nlog n))

    (如果后缀数组用(DC3)求解的话,时间复杂度会降为(Theta(n)))

    首先,将字符串调转过来,连接到原字符串的后面。然后在两个字符串交界处插入一个字典序最小的字符,以隔开两个字符串。(RT,)

    分两种情况讨论回文串1.长度为奇数的 2.长度为偶数的。

    如果长度是奇数,就查询后缀(i)和后缀(n-i)。看图自行理解。(注意,(n)整个字符串的长度)

    如果长度是偶数,就查询后缀(i)和后缀(n-i+1)。请看图自行理解。

    至于查询任意两个后缀的最长前缀,其实是查询这两个后缀的排名之间的(height)数组的最小值。(原因在此不进行阐述),用(RMQ)经过(Theta(n))的预处理,即可(Theta(1))的出答案。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 1005*2;
    int rak[maxn], newRank[maxn], sa[maxn];
    int sum[maxn], key2[maxn], height[maxn];
    int rmq[20][maxn], Log2[maxn];
    int n, m;
    char str[maxn];
    bool cmp(int a, int b, int l)
    {
    	if(rak[a] != rak[b]) return false;
    	if((a+l > n) xor (b+l > n)) return false;
    	if(a+l > n and b+l > n) return true;
    	return rak[a+l] == rak[b+l];
    }
    void makesa()
    {
    	for(int i=1; i<=n; i++) sum[rak[i] = str[i]]++;
    	for(int i=1; i<=m; i++) sum[i]+=sum[i-1];
    	for(int i=n; i>=1; i--) sa[sum[rak[i]]--] = i;
    	for(int l=1; l<n; l<<=1)
    	{
    		int k = 0;
    		for(int i=n-l+1; i<=n; i++) key2[++k] = i;
    		for(int i=1; i<=n; i++) if(sa[i] > l) key2[++k] = sa[i]-l;
    		for(int i=1; i<=m; i++) sum[i] = 0;
    		for(int i=1; i<=n; i++) sum[rak[i]]++;
    		for(int i=1; i<=m; i++) sum[i]+=sum[i-1];
    		for(int i=n; i>=1; i--) sa[sum[rak[key2[i]]]--] = key2[i];
    		int rk = 1;
    		newRank[sa[1]] = 1;
    		for(int i=2; i<=n; i++)
    		{
    			if(cmp(sa[i-1], sa[i], l)) newRank[sa[i]] = rk;
    			else newRank[sa[i]] = ++rk;
    		}
    		for(int i=1; i<=n; i++) rak[i] = newRank[i];
    		if(rk == n) break;
    	}
    }
    void getHeight()
    {
    	int k = 0;
    	for(int i=1; i<=n; i++)
    	{
    		if(rak[i] == 1) continue;
    		int j = sa[rak[i]-1];
    		while(str[j+k] == str[i+k] and i+k <= n and j+k <= n) k++;
    		height[rak[i]] = k;
    		if(k) k--;
    	}
    }
    void getRMQ()
    {
    	for(int i=1; i<=n; i++) rmq[0][i] = height[i];
    	for(int l=1; l<=20; l++)
    	   for(int i=1; i<=n; i++)
    	   {
    	   	  int j = min(i+(1<<(l-1)), n);
    	   	  rmq[l][i] = min(rmq[l-1][i], rmq[l-1][j]);
    	   }
    	for(int i=1; i<=n; i++)
    	{
    		if(i > (1<<Log2[i-1])<<1) Log2[i] = Log2[i-1]+1;
            else Log2[i] = Log2[i-1];
        }
    }
    int getmin(int l,int r)
    {
    	int len = Log2[r-l+1];
    	return min(rmq[len][l], rmq[len][r-(1<<len)+1]);
    }
    int check(int l, int r)
    {
    	int a = rak[l],  b = rak[r];
    	if(a > b) swap(a, b);
    	a++;
    	return getmin(a, b);
    }
    int main()
    {
    	scanf("%s", str+1);
    	n = strlen(str+1);
    	str[n+1] = '@';
    	for(int i=1, j=2*n+1; i<=n; i++, j--) str[j] = str[i];
    	n = n*2+1;
    	m = max(n, 256);
    	makesa();
    	getHeight();
    	getRMQ();
        int ans = 0, start = 0;
        for(int i=1; i<=n/2; i++)
        {
        	int len = check(i, n-i+1);
        	if(len*2-1 > ans) ans = len*2-1, start = i-len+1;
        	len = check(i, n-i+2);
        	if(len*2 > ans) ans = len*2, start = i-len;
    	}
    	for(int i=start, j = 1; j<=ans; j++, i++) printf("%c",str[i]);
    	return 0;
    }
    
  • 相关阅读:
    简单爬取腾讯新闻内容方法封装
    Python正则表达式函数
    Escape(反思与总结)
    springboot中使用mybatis
    解决 java.lang.UnsatisfiedLinkError:no ** in java.library.path in java.library.path 的异常
    解决 fatal error: jni_md.h: No such file or directory #include “jni_md.h”
    Git troubleshooting
    Java8 新特性2——强大的Stream API
    Java8 新特性1—— Lambda表达式、内置函数式接口、方法引用与构造器引用
    搭建Linux(Ubuntu)系统下的Differential Datalog运行环境
  • 原文地址:https://www.cnblogs.com/GDOI2018/p/10296315.html
Copyright © 2011-2022 走看看