字串最小表示初探 By cellur925
阿新 • • 發佈:2018-11-01
我們考慮有一個字串,可以從這個字串的不同位置出發,把這個字串大聲朗讀出來,當到字串末端的時候再從頭開始讀,直到回到“夢開始的地方”。
設字串長度為\(n\),那麼有\(n\)種不同的讀法。我們現在想要在這些讀法中找一個字串使得他字典序最小,如何快速求出?
我們當然可以用其他樸素的方法(這裡不再贅述),但其實我們有更高效的線性演算法:最小表示法。
演算法描述
首先把這個字串複製二倍接在後面(類似斷環為鏈)
然後利用兩個指標\(i=1\)和\(j=2\)在\(k=0\)的幫助下向後掃,當遇到\(i+k\)和\(j+k\)位置的字元不相等時,就退出。
如果\(i+k\)位置更大一些,直接把\(i\)
兩個指標不斷嘗試向後移動,一個移動到結尾就停止掃描,保證複雜度在\(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\)。