【JZOJ4644】【NOI2016模拟7.16】人生的经验 (hashing+欧拉回路)

Problem

  定义人生经验为长度为l,字符集大小为c的所有字符串,求一个最短的包含所有人生经验的字符串。

Hint

这里写图片描述

Solution

  这道题教会我们要善于猜结论熟练掌握欧拉回路。(其实我比赛时猜到是欧拉回路了,只不过忘了欧拉回路怎么破)不太懂欧拉回路的可以考虑戳一戳这里


  首先来算一下答案长度。
  char的大小为1bytes,10MB=10485760bytes,所以:

l e n ( a n s ) = 10485760 1 = 10485760 10 7

  再来计算一下长度上界。
  易知共有 c l 个人生经验。每个人生经验长度为l,故上界为 c l l
  当l=1时,人生经验数量有理论最大值10485760。换句话说, c l 最大为10485760。


  猜测结论:对于 c l 1 个人生经验,其后l-1个字符与另一个人生经验的前l-1个字符重合。这样就可以保证答案长度为下界: c l + l 1


  考虑转换模型。
  将由l-1个字符组成的字符串设为一个点;对于每个人生经验s,令s[1,l-1]代表的点向s[2,l]代表的点连一条字符边,边上字符为s[l]。比如有个人生经验为‘abc’,我们令‘ab’代表的点向‘bc’代表的点连一条字符为‘c’的边。
  当然,我们不可能直接将一个字符串作为点的编号。编号可以直接BKDR哈希。乘数直接设c,不用设模数,因为已知 c l 不可能超过10485760,而哈希只计算前l-1位, c l 1 也≤10485760。
  这样一来,每个点出度均为c;而每个点入度亦为c。根据判断条件,此图为欧拉图。


  于是直接上圈套圈算法。
  用一个栈记录每次走过的边,逆序输出。最后输出起点字符串。


  时间复杂度: O ( c l 1 + c l )

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();
}

猜你喜欢

转载自blog.csdn.net/qq_36551189/article/details/80919218