【最长回文子串】Hihocoder/Week1 学习笔记

二位关系真好……


小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进。
这一天,他们遇到了一连串的字符串,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能分别在这些字符串中找到它们每一个的最长回文子串呢?”
小Ho奇怪的问道:“什么叫做最长回文子串呢?”
小Hi回答道:“一个字符串中连续的一段就是这个字符串的子串,而回文串指的是12421这种从前往后读和从后往前读一模一样的字符串,所以最长回文子串的意思就是这个字符串中最长的身为回文串的子串啦~”
小Ho道:“原来如此!那么我该怎么得到这些字符串呢?我又应该怎么告诉你我所计算出的最长回文子串呢?
小Hi笑着说道:“这个很容易啦,你只需要写一个程序,先从标准输入读取一个整数N,代表我给你的字符串的个数,然后接下来的就是我要给你的那N个字符串啦。而你要告诉我你的答案的话,只要将你计算出的最长回文子串的长度按照我给你的顺序依次输出到标准输出就可以了!你看这就是一个例子。”


样例输入
3
abababa
aaaabaa
acacdas
样例输出
7
5
3

良久,小Ho仍然没有头绪,于是只能向小Hi求助。
小Hi清了清嗓子,缓缓说道:“让我从简单的说起吧,我给你一个字符串,你能不能告诉我它是不是一个回文串呢?”
小Ho回答道:“这个我当然可以啦!只要将这个字符串反过来,然后比较和原来的字符串是不是一样的不就行了?”
小Hi追问道:“也就是说你想要新建一个字符串咯?”
小Ho道:“那是当然,不然怎么比较呢?”
小Hi笑道:“但是你有没有注意到你在比较原来的字符串A和新字符串B的时候,A的第一个字符就是B的最后一个字符,而A的最后一个字符就是B的第一个字符,那么这样就比较了两次是不是浪费了效率呢?”
小Ho恍然道:“似乎是这样的!我知道了,我也不需要新建一个字符串了,我只需要比较A的第一个字符和最后一个字符是否相同,第二个字符和倒数第二个字符是否相同,以此类推,这样就只要比较字符串长度的一半次数就行了是不是?”
小Hi回答道:“没错!那你对于一个字符串,一一枚举它的子串,然后判断这个子串是不是回文子串,如果是的话就更新当前保存的最长的那一个,是不是就可以了?”
小Ho开心道:“是的!这个问题是不是就这么解决了?”

小Hi叹息道:“NONONO!你这最多也就拿个60分吧。”


int main()
{
	int n;	cin>>n;
	for(int i=0;i<n;i++)
	{
		string s;	cin>>s;
		string t=s;
		for(int j=0;j<s.length();j++)
		t[j]=s[s.length()-1-j];
		cout<<(s==t?"true":"false")<<endl;
	} 
	return 0;
}

小Ho遗憾的说道:“才及格啊,那我要怎么多拿点分呢?”
小Hi道:“不急不急,待我慢慢道来,你有没有想过之前的解法有没有什么问题?”
小Ho问道:“有什么问题?”
小Hi道:“你想想,如果一个字符串的[3, 7]这一段已经不是回文子串了,[2, 8]这一段还有可能是回文子串么?”
小Ho惊道:“好像不可能,那我之前不是有很多的计算都白费了,有没有什么办法来解决这个问题呢?我得好好想想!”言罢,小Ho沉思了起来。
良久,代表着成功的微笑出现来的小Ho的嘴边:“我知道了!我在枚举子串的时候换一种方式来进行枚举,不是枚举它的起止位置而是尝试枚举子串的中心位置,然后再从小到大依次枚举这个子串的长度,一旦发现已经不是一个回文串了就继续尝试下一个中心位置,这样,似乎就能够避免掉很多问题呢!”
小Hi赞许的点了点头,说道:“没错,这样的确会在一些情况下降低用于计算的时间呢,但是一个全是a的字符串,你这样的枚举方法似乎也没有多大用处呢?不过这样你也能拿个80分了哦!”

小Ho点了点头,说道:“没错,在最坏情况下,这种方法并没有比之前的方法好到哪里去,但是我的直觉告诉我肯定有更加高效的方法来进行计算呢,让我再好好想想吧!。” 


int main()
{
	int n;	cin>>n;
	for(int i=0;i<n;i++)
	{
		string s;	cin>>s;
		int flag=1,mid=s.length()/2;
		for(int i=0;i<=mid;i++)
		{
			if(s[mid-i]!=s[mid+i])
			{
				cout<<"false"<<endl;
				flag=0;
				break;
			}
		}
		if(flag) cout<<"true"<<endl;
	} 
	return 0;
}

小Ho这一想就是三天,小Hi也是看不下去了,决定来开导开导小Ho:“小Ho,你有没有想过,在之前的计算中,计算出以每一个位置为中心的最长回文子串的长度有没有什么用呢?”
小Ho答道:“我想想,如果以第5个字符为中心的最长回文子串的长度是5的话,这就告诉了我[3, 7]这一段是一个回文子串,所以呢?”
小Hi继续提示道:“假设这时候你想要计算以第6个字符为中心的最长回文子串的长度,你有没有什么已知的信息了?”
小Ho边想边说道:“唔,首先第6个字符和第4个字符是一样的,第7个字符和第3个字符是一样的,而第5个字符本身就肯定和第5个字符一样,那么如果[3, 5]这一段是回文子串的话,那么[5, 7]这一段肯定也是回文子串。也就是说,如果令f[i] 表示以第i个字符为中心的最长回文子串的长度,我们就会有f[i] >= f[i–2]?”
“不对,还要考虑到f[i – 1]的值,如果f[i – 1]太小就没有意义了,应该是f(i)≥min⁡{f(i-2), f(i-1)-2}。”小Ho接着补充道。
“没错,但是还有一个问题,如果此时我告诉你f(5) = 1,但是f(4) = 7, f(2) = 3呢?”小Hi追问道。
小Ho想了想,回答道:“理论上来说,我可以通过这些信息知道f(6)>=3,但是由于f(5)=1所以我只能计算出来f(6)>=-1我知道了,我不应该是通过f(i – 1)来辅助计算,而是通过使得右边界(j + f(j) / 2)最大的那个j来辅助计算才是,所以公式将变成 f(i) ≥ min{f(2*j-i) , f(j) -2*(i-j)}这种形式了!”
小Hi继续问道:“那知道了这个公式之后,你打算怎么做呢?”
小Ho想也没想便道:“这简单,我只要在之前枚举中心位置那种方法的基础上,统计使得回文串右边界(j + f(j) / 2)最大的那个j,然后再计算每一个i的时候,都可以通过f(i)≥min⁡{f(2*j-i), f(j)-2*(i-j)}这个公式来知道f(i)的一个最小值,这样即使是在我们所提到的那种最坏情况下,也可以节省掉很多不必要的计算呢~ 

 

一晃就是一周过去了,小Hi还是没有看到小Ho写的程序,于是决定上门去问问。到了小Ho家,小Hi惊讶的发现小Ho对着电脑屏幕,一脸郁闷的样子,于是他走上前问道:“小Ho小Ho,你怎么了啊?”
小Ho一点精神也没有的回答道:“就是上周的那个回文子串的程序啊,我写的时候发现我们当时考虑的解决方法只能处理长度为奇数的回文子串,长度为偶数的回文子串似乎要进行一点点细微的修改,但是这样修改过后就不能用我们最后写出的那个公式来互相帮助进行运算了,要进行很复杂的讨论,我就是一直在想有没有很优美的方法能来解决这个问题。”
小Hi惊讶道:“你就想这个想了一周么?我猜你一定是绕进了分类讨论的这个胡同里走不出来了,为什么不想想有没有别的解决方法呢?”
小Ho问道:“还有什么解决方法呀?”
小Hi答道:“既然长度为偶数的回文子串不好处理,我们为什么不去掉这些回文子串,只处理长度为奇数的回文子串呢?”
小Ho叹息道:“但是长度为偶数的回文子串也可以是答案啊!”
“除非……”小Hi插嘴道。
“除非什么?”小Ho问道。
“你将所有的长度为偶数的回文子串都变成长度为奇数的回文子串啊,你想想之所以是为偶数,那是因为没有一个中心字符,但是如果我们在原来字符串的基础上,在任意两个相邻的字符间都插入一个特殊字符,是不是无论是原来字符串中长度为奇数的回文子串还是长度为偶数的回文子串,在新的字符串中都有一个长度为奇数的回文子串与之进行对应呢?”
“哦!我了解了,这样我只需要对新的字符串按照我们之前的算法进行计算,统计出的最长回文子串将那些特殊字符去掉之后,就是原来字符串里的最长回文子串了。”小Ho开心的笑道,一连几天的郁闷也是一扫而空。
“但是要注意哦,我们希望得到的最长是原来字符串里的最长,而不是新字符串里的最长,毕竟特殊字符的个数会因为中心字符是不是特殊字符而有差别的哦。”小Hi提醒道。
“恩恩,这样我总能拿到满分了吧?”
“没错呢!” 

从HIHO/Week4才有十佳代码,所以并不能看看大大们的高超技巧以点拨提高,继续自己想,毕竟提示三+四一旦成功就是O(n)的算法了,可以理解但暂时还没有想通,没办法先暂存去打今天的Codeforces了……

【Pause Here】


然后看了看小HO的代码:

#include <cstdio>
#include <cstring>
#include <string.h>
char str[1000002 + 1200];

int fast(char *p)
{
    int ans = 1;
    for (int i = 1; p[i]; ++i)
    {
        int s = i, e = i, t;
        while (p[e + 1] == p[i]) ++e;
        i = e;
        while (p[s - 1] == p[e + 1]) --s, ++e;
        if ((t = e - s + 1) > ans) ans = t;
    }
    return ans;
}

int main()
{
    str[0]='$';
    int n;
    scanf("%d", &n);
    while (n --) {
        scanf("%s", str + 1);
        printf("%d\n", fast(str));
    }
    return 0;
}


大爷我给您跪下了……
你的承诺呢?你的担架呢?!!!……

[后记] 后来证实这个是在当时的hihoOJ上有人用这个魔性代码过了这些样例,小HO不信拿来跑了一遍,并不是小HO的官方认证代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

糖果天王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值