解释转自:https://blog.youkuaiyun.com/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());
}
}