zoukankan      html  css  js  c++  java
  • 【bzoj4974】字符串大师 逆模拟KMP

    zz:https://www.cnblogs.com/GXZlegend/p/7559669.html

    一个串T是S的循环节,当且仅当存在正整数k,使得S是Tk(即T重复k次)的前缀,比如abcd是abcdabcdab的循环节。给定一个长度为n的仅由小写字符构成的字符串S,请对于每个k(1<=k<=n),求出S长度为k的前缀的最短循环节的长度peri。字符串大师小Q觉得这个问题过于简单,于是花了一分钟将其AC了,他想检验你是否也是字符串大师。
    小Q告诉你n以及per1,per2,...,pern,请找到一个长度为n的小写字符串S,使得S能对应上per。
    输入
    第一行包含一个正整数n(1<=n<=100000),表示字符串的长度。
    第二行包含n个正整数per1,per2,...pern(1<=peri<=i),表示每个前缀的最短循环节长度。
    输入数据保证至少存在一组可行解。
    输出
    输出一行一个长度为n的小写字符串S,即某个满足条件的S。
    若有多个可行的S,输出字典序最小的那一个。
    样例输入

    5
    1 2 2 2 5

    样例输出

    ababb

    题解

    逆模拟KMP

    首先有个易证的常用结论:1~n的最短循环节长度等于n-next[n],其中next为KMP算法中的next数组。
    那么我们可以从前往后扫一遍。
    当next不等于0时,由于next的定义为最长公共前后缀的长度,因此可以直接在前面的部分找到(s[next[i]])。由于题目保证有解,因此无需验证其正确性。
    当next等于0时,考虑KMP算法求next的过程:对于上一个匹配位置,如果其下一个字符不等于当前字符,则当前匹配位置调整到其next的位置。如此循环直到下一个字符等于当前字符或者当前匹配位置为-1。然后next等于当前匹配位置+1。
    由于当前的next等于0,意味着上一个匹配位置的任意的next的下一个字符都不等于当前字符。此时只需要循环向前重复找next的过程,并把下一个位置的字符设为不可选择。由于要求字典序最小,所以当前字符即为可以选择的字符中字典序最小的字母。

    时间复杂度O(26n)

    Sol:
    输入
    9
    1 2 3 3 3 3 6 6 9
    输出
    abbabbabc
    Sol:
    先求出对应的next数组为
    0 0 0 1 2 3 1 0
    模拟数据如下
    对于第1个位置,这个位置其实必然是a。
    对于第2个位置,next[2]=0,说明它与字符串中next[2-1]+1也就是第0+1=1个字符即a是不一样的,所以按顺序取到b。
    对于第3个位置,next[3]=0,说明它与字符串中next[3-1]+1也就是第0+1=1个字符即a是不一样的,所以按顺序取到b。
    对于第4个位置,next[4]=1,说明这个位置上的字符与第1个字符是一样的,所以为a。
    对于第5个位置,next[5]=2,说明这个位置上的字符与第2个字符是一样的,所以为b
    .......
    对于第9个位置,next[9]=0,则说明它与字符串中next[9-1]+1也就是第2+1=3个字符即b是不一样的,然后与next[2]+1=1个字符即a也是不一样的,于是取c。

    #include <cstdio>
    #include <cstring>
    int next[100010] , vis[26];
    char str[100010];
    int main()
    {
        int n , i , j;
        scanf("%d" , &n);
        next[0] = -1;
        for(i = 1 ; i <= n ; i ++ )
        {
            scanf("%d" , &next[i]) , 
    		next[i] = i - next[i];//求出真实的next数组,对应于kmp中的 
            if(next[i]) 
    		    str[i] = str[next[i]];
            else
            {
                for(j = next[i - 1] ; ~j ; j = next[j]) 
    			    vis[str[j + 1] - 'a'] = i;
    			//next[i]是由next[i-1]推出来的的
    			//目前的第i位,应该与字符串中next[i-1]+1个字符去比较
    			//现在next[i]的值为0,说明与这些字符都不相等 
                for(j = 0 ; j < 26 ; j ++ )
                    if(vis[j] != i)
                        break;
                str[i] = j + 'a';
            }
        }
        printf("%s
    " , str + 1);
        return 0;
    }
    

      另一个好看一点的代码

    #include<bits/stdc++.h>
    using namespace std;
    int n,nxt[100001];
    char s[100001];
    bool flag[26];
    int main() {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) //求出真实的nxt数组 
    	{
            scanf("%d",nxt+i);
            nxt[i]=i-nxt[i];
        }
        s[1]='a'; //第一个位置当然是放a 
        for(int i=2;i<=n;i++)//一路扫过去 
    	{
            if(nxt[i])//如果大于零,可直接算出当前这一位的字母是哪一个 
    		{
                s[i]=s[nxt[i]];
            }
            else
    		{
    			//如果为0,则说明有些字母是不能取的 
                int p=nxt[i-1]+1;//P代表,当前字符不能为字符串中第P个字符 
                memset(flag,false,sizeof(flag));
                flag[0]=true;//字母a已取过了 
                while(p!=1)
    			{
                    flag[s[p]-'a']=true;
                    p=nxt[p-1]+1;
                }
                for(int j=0;j<26;j++) // 找出第一个能取的字符出来 
    			{
                    if(!flag[j])
    				{
                        s[i]=j+'a';
                        break;
                    }
                }
            }
        }
        s[n+1]='';
        printf("%s
    ",s+1);
        return 0;
    }
    

      

  • 相关阅读:
    poj 1684 Lazy Math Instructor(字符串)
    STL内存配置器
    迭代器(iterators)
    类型萃取(type traits)
    hdu 2191 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活(多重背包+dp)
    hdoj 1114 Piggy-Bank(完全背包+dp)
    hdoj 2546 饭卡(0-1背包)
    hdoj 2620 Bone Collector(0-1背包)
    U3d开发个人总结
    Android软键盘的用法总结
  • 原文地址:https://www.cnblogs.com/cutemush/p/12384229.html
Copyright © 2011-2022 走看看