hdu - 4333 - Revolving Digits - 扩展kmp

扩展的KMP算法,这个算法作为KMP的扩展,可以说是包含KMP的。它求出了一组数值,extend[i]表示A串中以i开始的后缀(从i到lena的子串)与B串的最长公共前缀(从头数到不一样的字符)的长度,也就是LCP。next[i]表示T[i..m]与T的最长公共前缀长度,也就是自匹配的长度。设extend[0..k-1]已经算好,并且在以前的匹配过程中到达的最远位置是p-1。最远位置严格的说就是i+extend[i]-1的最大值,其中i=0,1,2,3,…,k-1;设取最大值的i是a。则有A[a~p-1] == B[0~p-a-1],可以推出有A[k~p-1] == B[k - a ~ p -1 - a] , 令L = next[k-a],若k+L<p,则extend[k]=L, 否则继续判断A[p]和B[p-k],直到不相等,这时需要更新extend[k]和a值。

http://acm.hdu.edu.cn/showproblem.php?pid=4333

void build_next()
{
    int k, q, p, a;
    next[0] = len_t;
    for (k = 1, q = -1; k < len_t; k ++, q –)
    {
        if (q < 0 || k + next[k - a] >= p)
        {
            if (q < 0)q = 0, p = k;
//q是B串继续向后匹配的指针,p是A串继续向后匹配的指针,也是曾经到达过的最远位置+1
//q在每次计算后会减小1,直观的讲就是B串向后错了一位
            while (p < len_t && T[p] == T[q])
            {
                p ++, q ++;
            }
            next[k] = q, a = k;
        }
        else
        {
            next[k] = next[k - a];
        }
    }
}
void extend_KMP()
{
    int k, q, p, a;
    for (k = 0, q = -1; k < len_s; k ++, q –)
    {
        if (q < 0 || k + next[k - a] >= p)
        {
            if (q < 0)q = 0, p = k;
            while (p < len_s && q < len_t && S[p] == T[q])
            {
                p ++, q ++;
            }
            extend[k] = q, a = k;
        }
        else
        {
            extend[k] = next[k - a];
        }
    }
}

题解:
本题重点是比较x经过数位轮换之后的每个数和x的大小关系。由于x很大,我们需要将x以及轮换后的每个数视为一个长度为x长度的字符串,然后进行字符串比较。也就是说,我们可以将x后面接上一个x,变为xx,然后比较xx前n位以每一位开头长度为n的字符串与xx前n位的大小。如果我们能够很快求出xx的每一位和xx的最长公共前缀的长度的话,比较大小我们只需要O(1)的时间,而求出每一位的最长公共前缀可以使用扩展KMP算法在O(n)时间内求得,这样就可以在O(n)时间内完成所有的比较。
除了比较之外,本题还需要对于数字进行判重,经过观察我们发现,轮换出现重复数字的情况说明原来的x存在循环节,我们只需要找出x的最小循环节,然后只比较循环节之内的数即可。而求最小循环节也可以通过扩展KMP算法或者KMP算法得出,这样整体时间复杂度为O(n)。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<queue>
#include<cstring>
#include<set>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
const int N=200005;
char s[N];
int next[N];//next 指向的是以此点开始的字符串应该与原串中的哪个字符进行比较
// 当然比较的时候此串也要找对应的字符进行比较 如果超过字符串长度 说明相等
void getnext(int n)
{
    int l=1,r=-1;//l 和 r 代表循环串的左右位置
    next[0]=0;
    for(int i=1;i<n;++i)
    {//next[i]表示s与s[i..n]的最长公共前缀的长度
        if(i+next[i-l]<=r)
        {
            next[i]=next[i-l];//如果前面对应的循环串对应位置字符要找的位置对应到本串中字符要找的位置没有超过r  说明可以直接将答案copy过来
        }else
        {
            int j=max(r-i+1,0);//否则选择可以最靠后开始的位置进行依次比较 直到找到不同的 并记录循环串位置
            for(;j+i<2*n;++j)
            if(s[j]!=s[i+j])
            break;
            next[i]=j;
            l=i;
            r=i+j-1;
        }
    }
}
int main()
{
   int T;
   //freopen("data.txt","r",stdin);
   scanf("%d",&T);
   getchar();
   for(int c=1;c<=T;++c)
   {
        gets(s);
        int n=strlen(s);
        for(int i=0;i<n;++i)
        {
            s[i+n]=s[i];
        }
        s[n*2]=0;//copy2
        
        getnext(n);
        
        int i;
        for(i=1;i<n;++i)
            if(i+next[i]>=n)
                break;//找连续循环串位置  
        
        int m=(n%i)?m:i;//如果可以除进  则只要环内部分
        int l=0,q=1,g=0;
        for(i=1;i<m;++i)
        {
            if(next[i]>=n)
            {
                ++q;
            }
            else if(s[next[i]]>s[i+next[i]])
            {
                ++l;
            }else
            {
                ++g;
            }
        }
        printf("Case %d: %d %d %d\n",c,l,q,g);
   }
   return 0;
}


发布了177 篇原创文章 · 获赞 2 · 访问量 18万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章