1. 程式人生 > >字串最小表示初探 By cellur925

字串最小表示初探 By cellur925

我們考慮有一個字串,可以從這個字串的不同位置出發,把這個字串大聲朗讀出來,當到字串末端的時候再從頭開始讀,直到回到“夢開始的地方”。

設字串長度為\(n\),那麼有\(n\)種不同的讀法。我們現在想要在這些讀法中找一個字串使得他字典序最小,如何快速求出?

我們當然可以用其他樸素的方法(這裡不再贅述),但其實我們有更高效的線性演算法:最小表示法。

演算法描述

  1. 首先把這個字串複製二倍接在後面(類似斷環為鏈)

  2. 然後利用兩個指標\(i=1\)\(j=2\)\(k=0\)的幫助下向後掃,當遇到\(i+k\)\(j+k\)位置的字元不相等時,就退出。

  3. 如果\(i+k\)位置更大一些,直接把\(i\)

    跳到\(i+k+1\)。因為可以證明從\(i+1\)\(i+k\)都不是字串的最小表示,掃這部分就是冗餘的。

  4. 兩個指標不斷嘗試向後移動,一個移動到結尾就停止掃描,保證複雜度在\(O(n)\)內。

void work()
{
    n=strlen(str+1);ans=0;
    for(int i=1;i<=n;i++) str[n+i]=str[i];
    int i=1,j=2,k;
    while(i<=n&&j<=n)
    {
        for(k=0;k<=n&&str[i+k]==str[j+k];k++);
        if(k>=n) break;
        if(str[i+k]>str[j+k])
        {i=i+k+1;if(i==j) i++;}
        else
        {j=j+k+1;if(i==j) j++;}
    }
    ans=min(i,j);
    printf("%d\n",ans);
}

兩道新鮮熱乎的例題

例1:bzoj1398尋找主人

給你兩個字串,問你他們是否同構,若同構輸出最小表示。

第二問是裸題,第一問我們只要分別求出兩個字串的最小表示看他們是否相同即可。

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>

using namespace std;

bool flag;
int pos1,pos2,len;
char s1[3000000],s2[3000000];

void work1()
{
    len=strlen(s1+1);
    for(int i=1;i<=len;i++) s1[i+len]=s1[i];
    int i=1,j=2,k;
    while(i<=len&&j<=len)
    {
        for(k=0;k<=len&&s1[i+k]==s1[j+k];k++);
        if(k>=len) break;
        if(s1[i+k]>s1[j+k])
        {
            i=i+k+1;
            if(i==j) i++;
        } 
        else
        {
            j=j+k+1;
            if(i==j) j++;
        } 
    }
    pos1=min(i,j);
}

void work2()
{
    for(int i=1;i<=len;i++) s2[i+len]=s2[i];
    int i=1,j=2,k;
    while(i<=len&&j<=len)
    {
        for(k=0;k<=len&&s2[i+k]==s2[j+k];k++);
        if(k>=len) break;
        if(s2[i+k]>s2[j+k])
        {
            i=i+k+1;
            if(i==j) i++;
        } 
        else
        {
            j=j+k+1;
            if(i==j) j++;
        } 
    }
    pos2=min(i,j);
}

int main()
{
    scanf("%s",s1+1);
    scanf("%s",s2+1);
    work1();work2();
    int i=pos1,j=pos2,cnt=1;
    while(cnt<=len)
    {
        if(s1[i]==s2[j]) i++,j++,cnt++;
        else {flag=1;break;}
    }
    if(flag)
    {
        printf("No\n");
        return 0;
    }
    else printf("Yes\n");
    cnt=1;i=pos1;
    while(cnt<=len) cout<<s1[i],i++,cnt++;
    return 0;
}

例2 poj1509

給你很多字串,求出他們最小表示的起點位置。

真·裸題。

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

int n,Q,ans;
char str[30000];

void work()
{
    n=strlen(str+1);ans=0;
    for(int i=1;i<=n;i++) str[n+i]=str[i];
    int i=1,j=2,k;
    while(i<=n&&j<=n)
    {
        for(k=0;k<=n&&str[i+k]==str[j+k];k++);
        if(k>=n) break;
        if(str[i+k]>str[j+k])
        {i=i+k+1;if(i==j) i++;}
        else
        {j=j+k+1;if(i==j) j++;}
    }
    ans=min(i,j);
    printf("%d\n",ans);
}

int main()
{
    scanf("%d",&Q);
    while(Q--)
    {
        scanf("%s",str+1);
        work();
    }
    return 0;
}

感覺這種演算法可擴充套件性不太強(?),不過當個暴力工具就好了\(qwq\)