解釋轉自:https://blog.csdn.net/tianyuhang123/article/details/54919715
用於求一個字符串(首尾相連)的最小字典序的下標。
暴力(n*n)
【線性算法】O(N):
初始時,讓i=0,j=1,k=0,其中i,j,k表示的是以i開頭和以j開頭的字符串的前k個字符相同
分爲三種情況
1.如果str[i+k]==str[j+k] k++。
2.如果str[i+k] > str[j+k] i = i + k + 1,即最小表示不可能以str[i->i+k]開頭。
3.如果str[i+k] < str[j+k] j = j + k + 1,即最小表示不可能以str[j->j+k]開頭。
那麼只要循環n次,就能夠判斷出字符串的最小表示是以哪個字符開頭。
爲什麼當str[i+k] > str[j+k] i = i + k + 1,最小表示不可能以str[i->i+k]開頭,讓我們來舉個栗子。
如下圖,當i=1,j=5,k=3時,str[i+k] > str[j+k]。
首先有S1S2S3 == S5S6S7,S4 > S8。
那麼以字符S2開頭肯定不如以字符S6開頭更優,因爲S4 > S8啊。
最小表示法:
int get_min(string s)
{
int k=0,i=0,j=1;
int n=s.size();
while(k<n&&i<n&&j<n)
{
if(s[(i+k)%n]==s[(j+k)%n])
k++;
else
{
s[(i+k)%n]>s[(j+k)%n]?i=i+k+1:j=j+k+1;
if(i==j)
i++;
k=0;
}
}
i=min(i,j);
return i;
}
最大表示法:
int get_max(string s)
{
int k=0,i=0,j=1;
int n=s.size();
while(k<n&&i<n&&j<n)
{
if(s[(i+k)%n]==s[(j+k)%n])
k++;
else
{
s[(i+k)%n]>s[(j+k)%n]?j=j+k+1:i=i+k+1;
if(i==j)
i++;
k=0;
}
}
i=min(i,j);
return i;
}
基礎練習題:zoj 1729 https://vjudge.net/problem/ZOJ-1729
string ss[220];
int get_num(string s)
{
int k=0,i=0,j=1;
int n=s.size();
while(k<n&&i<n&&j<n)
{
if(s[(i+k)%n]==s[(j+k)%n])
k++;
else
{
s[(i+k)%n]>s[(j+k)%n]?i=i+k+1:j=j+k+1;
if(i==j)
i++;
k=0;
}
}
i=min(i,j);
return i;
}
int main()
{
int t,cnt;
scanf("%d",&t);
while(t--)
{
string s;
cin>>cnt>>s;
int p=get_num(s);
printf("%d\n",p);
}
return 0;
}
變形題,昨天剛搞的比賽:19牛客暑期多小訓練營(第七場)A String https://ac.nowcoder.com/acm/contest/887/A
題意:
將一個字符串拆分成最少的子串,使得該字串在其本身的循環串(首尾相連)中爲字典序最小的那一個。
思路:可以使用最小表示法, 從後向前, 找到當前字典序最小的循環串位置,注意找到的串, 仍要繼續判斷是否爲當前最小循環串, 直到滿足條件即可以分割
string ss[220];
int get_num(string s)
{
int k=0,i=0,j=1;
int n=s.size();
while(k<n&&i<n&&j<n)
{
if(s[(i+k)%n]==s[(j+k)%n])
k++;
else
{
s[(i+k)%n]>s[(j+k)%n]?i=i+k+1:j=j+k+1;
if(i==j)
i++;
k=0;
}
}
i=min(i,j);
return i;
}
int main()
{
int t,cnt;
scanf("%d",&t);
while(t--)
{
cnt=0;
string s;
cin>>s;
int len=s.size();
int i=len;
for(;;)
{
if(i<=0)
break;
int k=get_num(s);
string now=s.substr(k);
int kn=0;
while(true)
{
kn=get_num(now);
now=now.substr(kn);
if(kn==0)
break;
}
ss[++cnt]=now;
int len1=now.size();
i-=len1;
s=s.substr(0,i);
}
for(int i=cnt;i>=1;i--)
{
if(i!=1)
cout<<ss[i]<<" ";
else
cout<<ss[i];
}puts("");
}
return 0;
}
2019.8.14補
例題3:hdu-2609
題意:多組輸入,每組給你n個字符串,問你有幾個不相同的字符串(一個字符串和其循環字符串相同,例如 1001->0011->0110->1100)
思路:利用最小表示法將字符串轉換爲它的字典序最小的形式,然後將其插入set容器裏去重就行了。
#include <bits/stdc++.h>
using namespace std;
int tao(string s)
{
int k=0,i=0,j=1;
int n=s.size();
while(k<n&&i<n&&j<n)
{
if(s[(i+k)%n]==s[(j+k)%n])
k++;
else
{
s[(i+k)%n]>s[(j+k)%n]?i=i+k+1:j=j+k+1;
if(i==j)
i++;
k=0;
}
}
i=min(i,j);
return i;
}
set<string>st;
int main()
{
int n;
while(~scanf("%d",&n))
{
st.clear();
string s;
for(int i=0; i<n; i++)
{
cin>>s;
int p=tao(s);
string a=s.substr(p)+s.substr(0,p);
//從p直至串尾 截取p個字符0->p-1
st.insert(a);
}
printf("%d\n",st.size());
}
}