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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章