Tricky and Clever Password 【KMP+Manacher】【蓝桥杯试题】

问题描述
  在年轻的时候,我们故事中的英雄——国王 Copa——他的私人数据并不是完全安全地隐蔽。对他来说是,这不可接受的。因此,他发明了一种密码,好记又难以破解。后来,他才知道这种密码是一个长度为奇数的回文串。

  Copa 害怕忘记密码,所以他决定把密码写在一张纸上。他发现这样保存密码不安全,于是他决定按下述方法加密密码:他选定一个整数 X ,保证 X 不小于 0 ,且 2X 严格小于串长度。然后他把密码分成 3 段,最前面的 X 个字符为一段,最后面的 X 个字符为一段,剩余的字符为一段。不妨把这三段依次称之为 prefix, suffix, middle 。显然, middle 的长度为一个大于 0 的奇数,且 prefix 、 suffix 的长度相等。他加密后的密码即为 A + prefix + B + middle + C + suffix ,其中 A 、 B 、 C 是三个由 Copa 选定的字符串,且都有可能为空, + 表示字符串相连。

  许多年过去了。Copa 昨天找到了当年写下加密后字符串的那张纸。但是,Copa 把原密码、A、B、C 都忘了。现在,他请你找一个尽量长的密码,使得这个密码有可能被当年的 Copa 发明、加密并写下。
输入格式
  输入包含一个只含有小写拉丁字母的字符串,长度在 1 到 10^5 之内。
输出格式
  第一行包含一个整数 k ,表示你找到的原密码分成的 3 个部分中有多少个非空字符串。显然 k in {1, 3} 。接下来 k 行,每行 2 个用空格分开的整数 x_i l_i ,表示这一部分的起始位置和长度。要求输出的 x_i 递增。

  起始位置 x_i 应该在 1 到加密后的字符串长度之间。 l_i 必须是正整数,因为你只要输出非空部分的信息。 middle 的长度必须为奇数。

  如果有多组答案,任意一组即可。提示:你要最大化的是输出的 l_i 的总和,而不是 k 。
样例输入
abacaba
样例输出
1
1 7
样例输入
axbya
样例输出
3
1 1
2 1
5 1
样例输入
xabyczba
样例输出
3
2 2
4 1
7 2
数据规模和约定
  对于 10% 的数据: n <= 10

  对于 30% 的数据: n <= 100

  对于 100% 的数据: n <= 100000

  存在 20% 的数据,输出文件第一行为 1 。

#include<stdio.h>
#include<string.h>
#define INF 0x3f3f3f3f
char s[100010], s1[200010];
int mana[100010], L, next[200010], match[100010], r[100010];
int min(int x, int y)
{
	return x<y?x:y;
} 
void getmana()
{
	mana[0] = 0;
	int id = 0, right = 0;
	for(int i = 1; i < L-1; ++i)
	{
		if(right > i)
		mana[i] = min(mana[2*id - i], right - i);
		else
		mana[i] = 0;
		while(s[i+mana[i]+1] == s[i-mana[i]-1]  && (i+mana[i]+1) < L && (i-mana[i]-1) >= 0)
		mana[i]++;
		if(mana[i]+i > right)
		{
			id = i;
			right = mana[i]+i;
		}
	}
} 
void getnext(char *temp)
{
	memset(next, 0, sizeof(next));
	next[0] = -1;
	int i = 0, j = -1;
	while(temp[i])
	{
		if(j == -1 || temp[i] == temp[j])
		{
			i++;
			j++;
			next[i] = j;
		}
		else
		j = next[j];
	}
}

int main()
{
	int i, j, k, l;
	while(gets(s) != NULL)
	{
		L = strlen(s);
		getmana();
		for(i = L-1; i >= 0; --i)
		s1[L-1-i] = s[i];
		s1[L] = '#';
		s1[L+1] = '\0';
		strcat(s1,s); 
		getnext(s1);
		for(i = L+1; i <= 2*L+1; ++i)
		match[i-L-1] = next[i];
		memset(r, -1, sizeof(r));
		for(i = 1; i <= L; ++i)
		{
			if(match[i] > match[i-1])
			r[match[i]-1] = i-1;
			else
			match[i] = match[i-1];
		}
		for(i = 1; i < L; ++i)
		{
			if(r[i] == -1)
			r[i] = r[i-1];
	//		printf("%d ", r[i]); 
		} 
	//	printf("\n");
		int ans[3][2] = {0}, max = 0;
		bool ok = true;
		for(i = 1; i < L-1; ++i)//the length must bigger than 3
		{
			j = i+mana[i]+1;
			if(j >= L || i-mana[i] <= 0)
			j = L;
			else
			{
				if(r[L-j-1] == -1)
				j = L;
				else
				{
					if(r[L-j-1] >= i-mana[i])
					j = L - match[i-mana[i]];
					else
					j = L - match[r[L-j-1]+1];
				}
			} 
			if(j < L)
			{
				if(2*(L-j) + mana[i]*2 + 1 > max)
				{
					max = 2*(L-j) + mana[i]*2 + 1;
					ans[0][0] = r[L-j-1] - L + j + 1;
					ans[0][1] = L-j;
					ans[1][0] = i-mana[i];
					ans[1][1] = 2*mana[i]+1;
					ans[2][0] = j;
					ans[2][1] = L-j;
					ok = false;
			//		printf("1 %d\n", max);
				} 
			}
			else
			{
				if(2*mana[i]+1 > max)
				{
					max = 2*mana[i]+1;
					ok = true;
					ans[1][0] = i-mana[i];
					ans[1][1] = 2*mana[i]+1;
				}
			}
		}
		if(ok)
		{
			if(ans[1][1] == 0)
			ans[1][1] = 1; 
			printf("1\n%d %d\n", ans[1][0]+1, ans[1][1]);
		}
		else
		{
			printf("3\n");
			printf("%d %d\n", ans[0][0]+1, ans[0][1]);
			printf("%d %d\n", ans[1][0]+1, ans[1][1]);
			printf("%d %d\n", ans[2][0]+1, ans[2][1]);
		}
	}
	return 0;
} 

题意:给出一个字符串,该字符串由A + prefix + B + middle + C + suffix 六个子字符串 构成,其中A,B,C可以为空,middle必定为奇数长度的回文串,prefix与 suffix 为一个回文串的两半, prefix +middle+ suffix,为题目所说的密码,现在求这三个部分在输入的字符串中的开始位置以及长度,求密码长度最大的情况下的答案。

思路:总体思路为枚举字符串中以每个字符为中心的middle部分,然后向两边寻找prefix以及suffix。

首先对于middle我们需要用manacher算法求出每个字符为中心时的middle。

其次我们从题意可以看出suffix必定是从输入的字符串的最后一个字符开始往前数的,那么我们可以求出对于每一个middle的剩下的尾部所能匹配到的最大的prefix。

这也是这道题目的难点所在。

我们先把字符串反转,然后再接上原字符串,并且在2个字符串中间插上一个不相干字符作为分界符,让必定成为suffix的部分成为新字符串的前端。

然后我们求出新字符串的next数组,想一想对于后半部分来说,next所代表的含义:即对于源字符串顺序匹配时所能匹配到的源字符串逆向部分的最大长度。(保存到一个新的数组中,假设为a数组)

这样我们就可以根据a部分再进行反向处理,得到b数组:即从源字符串的第i个字符开始往后的所有部分组成的字符串 所能匹配到的 源字符串的正向部分最大长度的字符串的最右端的位置。

最后对于每一个middle 我们就可以知道它最长的prefix和suffix,这里根据suffix来判断prefix,①如果说prefix的最右端的位置覆盖了middle,那么middle最长的prefix即为0~middle的最左端,由prefix我们又可以根据a知道suffix的最大长度,三个部分求得;②如果说prefix的最右端的位置小于middle,那么我们就知道了prefix,同理根据prefix也可以求出suffix三个部分求得。

你可能感兴趣的:(Tricky and Clever Password 【KMP+Manacher】【蓝桥杯试题】)