Problem
定义人生经验为长度为l,字符集大小为c的所有字符串,求一个最短的包含所有人生经验的字符串。
Hint
Solution
这道题教会我们要善于猜结论熟练掌握欧拉回路。(其实我比赛时猜到是欧拉回路了,只不过忘了欧拉回路怎么破)不太懂欧拉回路的可以考虑戳一戳这里。
首先来算一下答案长度。
char的大小为1bytes,10MB=10485760bytes,所以:
再来计算一下长度上界。
易知共有 个人生经验。每个人生经验长度为l,故上界为 。
当l=1时,人生经验数量有理论最大值10485760。换句话说, 最大为10485760。
猜测结论:对于 个人生经验,其后l-1个字符与另一个人生经验的前l-1个字符重合。这样就可以保证答案长度为下界: 。
考虑转换模型。
将由l-1个字符组成的字符串设为一个点;对于每个人生经验s,令s[1,l-1]代表的点向s[2,l]代表的点连一条字符边,边上字符为s[l]。比如有个人生经验为‘abc’,我们令‘ab’代表的点向‘bc’代表的点连一条字符为‘c’的边。
当然,我们不可能直接将一个字符串作为点的编号。编号可以直接BKDR哈希。乘数直接设c,不用设模数,因为已知
不可能超过10485760,而哈希只计算前l-1位,
也≤10485760。
这样一来,每个点出度均为c;而每个点入度亦为c。根据判断条件,此图为欧拉图。
于是直接上圈套圈算法。
用一个栈记录每次走过的边,逆序输出。最后输出起点字符串。
时间复杂度: 。
Code
#include <cstdio>
#include <cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int N=105e5;
int i,c,l,f,sta[N],top,cur[N>>1],as;
bool bz;
char s[26],tr[N],ans[N];
void print()
{
printf("%d\n",as+l-1);
fo(i,1,l-1)putchar(s[0]);
while(as)putchar(ans[as--]);
}
void Euler()
{
int i,x,y;
sta[top=1]=0;
while(top)
{
x=sta[top];
if(cur[x]<c)
{
i=cur[x]; cur[x]=i+1;
sta[++top]=y=x%f*c+i; tr[top]=s[i]; continue;
}
ans[++as]=tr[top--];
}
as--;
}
int main()
{
freopen("life.in","r",stdin);
freopen("life.out","w",stdout);
scanf("%d%d%s",&c,&l,s);
if(l==1)
{
printf("%d\n",c);
fo(i,0,c-1) putchar(s[i]);
return 0;
}
f=pow(c,l-2);
Euler();
print();
}