算法训练 Tricky and Clever Password
时间限制:2.0s 内存限制:256.0MB
问题描述
在年轻的时候,我们故事中的英雄——国王 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 发明、加密并写下。
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 。
起始位置 x_i 应该在 1 到加密后的字符串长度之间。 l_i 必须是正整数,因为你只要输出非空部分的信息。 middle 的长度必须为奇数。
如果有多组答案,任意一组即可。提示:你要最大化的是输出的 l_i 的总和,而不是 k 。
样例输入
abacaba
样例输出
1
1 7
1 7
样例输入
axbya
样例输出
3
1 1
2 1
5 1
1 1
2 1
5 1
样例输入
xabyczba
样例输出
3
2 2
4 1
7 2
2 2
4 1
7 2
数据规模和约定
对于 10% 的数据: n <= 10
对于 30% 的数据: n <= 100
对于 100% 的数据: n <= 100000
存在 20% 的数据,输出文件第一行为 1 。
对于 30% 的数据: n <= 100
对于 100% 的数据: n <= 100000
存在 20% 的数据,输出文件第一行为 1 。
思路:
考虑枚举中间的长度为奇数的回文串,计算出两边最多能匹配出多长的串,每次得到一个长度值,最后更新即可。
枚举中间的回文串时先用manacher预处理一下,再枚举即可。
两边的串比较麻烦,可以按如下方法求:
考虑到suffix是题目给出的字符串S的后缀(而prefix不一定是前缀),所以可以先将原串翻转得S',这样方便用KMP之类的算法预处理。
这样定义一个数组match[]:
match[i]表示在S'中使得S'[0] = S'[i+k-1]、S'[1] = S'[i+k-2]、S'[2] = S'[i+k-3] ...... S'[k-1] = S'[i] 成立的最大的k。
举个例子,若S' = abcda 则match[0] = 5, match[1] = 0, match[2] = 0, match[4] = 1
怎么求match[i]?
可以构造一个字符串SS = S'+'$'+S,即把S'和S连接起来,中间用一个特殊字符分隔,这样对SS求出KMP中的next数组,通过next数组的后半部分就能求出match[]
有了match数组问题就解决了,假设每次枚举中间的回文串得到一个回文串S'[l, r],那么只要求出match数组在r+1到n内的最大值就得到了prefix和suffix的长度L,这个可以通过预处理得到。
很chuo的代码:
#include<cstdio>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <algorithm>
#include <set>
#include <map>
#include <stack>
using namespace std;
#define N 101000
#define ll long long
#define INF 2100000000
const double pi = acos(-1);
double eps = 1e-8;
char init_str[N], str[N], mnc_str[N], cmb_str[2*N];
int len[N], mnc_len;
int a[3][2];
int nxt[2*N];
int match[N], rgt_max_match[N];
void manacher(char *init_str, int n)
{
mnc_str[0] = '$';
mnc_str[1] = '#';
int cnt = 2;
for(int i = 0; i < n; i++)
{
mnc_str[cnt++] = init_str[i];
mnc_str[cnt++] = '#';
}
mnc_str[cnt] = '\0';
int id = 0, max_r = 0;
for(int i = 1; i < cnt; i++)
{
if(i < max_r) len[i] = min(len[2*id-i], max_r-i+1);
else len[i] = 1;
for(; i+len[i] < cnt && i-len[i] >= 0 && mnc_str[i+len[i]] == mnc_str[i-len[i]]; len[i]++);
if(i+len[i]-1 > max_r)
{
max_r = i+len[i]-1;
id = i;
}
}
mnc_len = cnt;
}
void get_kmp_next(char* str, int n)
{
nxt[0] = -1;
for(int i = 1; i < n; i++)
{
int p = nxt[i-1];
while(p >= 0 && str[p+1] != str[i]) p = nxt[p];
p = str[p+1] == str[i] ? p+1 : -1;
nxt[i] = p;
}
}
void pre_proccess(int n)
{
for(int i = 1; i <= n; i++)
{
match[n-i] = nxt[n+i]+1;
}
rgt_max_match[n-1] = n-1;
for(int i = n-2; i >= 0; i--)
{
if(match[i] > match[rgt_max_match[i+1]])
rgt_max_match[i] = i;
else rgt_max_match[i] = rgt_max_match[i+1];
}
}
int main()
{
//freopen("C:\\Users\\zfh\\Desktop\\in.txt", "r", stdin);
while(scanf(" %s", init_str) != -1)
{
int n = strlen(init_str);
int cmb_cnt = 0;
for(int i = 0; i < n; i++)
cmb_str[cmb_cnt++] = init_str[n-i-1];
cmb_str[cmb_cnt++] = '$';
for(int i = 0; i < n; i++)
cmb_str[cmb_cnt++] = init_str[i];
get_kmp_next(cmb_str, cmb_cnt);
for(int i = 0; i < n/2; i++)
swap(init_str[i], init_str[n-i-1]);
manacher(init_str, n);
pre_proccess(n);
int maxv = -1, flag;
for(int i = 2; i < mnc_len; i+=2)
{
int l = (i-len[i]+1)/2, r = (i+len[i]-1)/2-1;
if(r < l) continue;
int L;
if(r+1 >= n || l <= 0)
L = 0;
else
L = min(match[rgt_max_match[r+1]], l);
int sum_len = r-l+1 + 2*L;
if(sum_len >= maxv)
{
maxv = sum_len;
if(L == 0)
{
flag = 0;
a[1][0] = l+1;
a[1][1] = r-l+1;
}
else
{
flag = 1;
a[0][0] = 1;
a[0][1] = L;
a[1][0] = l+1;
a[1][1] = r-l+1;
a[2][0] = rgt_max_match[r+1]+1;
a[2][1] = L;
}
}
}
if(flag)
{
printf("3\n");
for(int i = 0; i < 3; i++)
{
printf("%d %d\n", n-a[3-i-1][0]+2-a[3-i-1][1], a[3-i-1][1]);
}
}
else
{
printf("1\n");
printf("%d %d\n", n-a[1][0]+2-a[1][1], a[1][1]);
}
}
return 0;
}