@description@
给定一个由小写字母构成的圆环形的字符串(即首字母和末字母是相连的)。
让你找到一个位置将这个圆环形的串断开成为一个序列形的串,使得这个字符串字典序最小。如果有多个,输出位置最靠前的那一个。
input
多组数据。第一行输入数据组数 N。
接下来 N 行,每行一个长度不超过 10000 个字符的字符串。保证仅由小写字母组成。
output
对于每组数据,输出一个位置。表示以这个位子作为字符串起点字符串的字典序最小。
sample input
4
helloworld
amandamanda
dontcallmebfu
aaabaaa
sample output
10
11
6
5
@solution@
据说这道题可以用什么……最小表示法来做。不过我觉得反正后缀自动机也不难写,就直接用后缀自动机吧。
圆环嘛,我们就把原字符串 S 后面再拼一个 S ,然后求 SS 长度为 |S| 且字典序最小的串嘛。
后缀自动机中每一个结点都可以表示若干子串。
那我从起始结点出发,每次总是沿着字典序最小的边走,走 |S| 步不就可以了嘛。
它还要求位置最靠前。后缀自动机里面跟位置有关的,就是 end-pos 嘛。那我就是 end-pos 集合里面最小的那个再减去字符串长度就可以了嘛。
跟求解子串出现次数一样,我们把每次通过增量法增加的结点视为有效结点,再通过桶排序向 fa 转移,转移时取最小值即可。
@accepted code@
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF = (1<<30);
const int MAXN = 10000;
struct sam{
sam *fa, *ch[26]; int mx;
sam *nxt; int mnps;
}pl[4*MAXN + 5], *bin[2*MAXN + 5], *tcnt, *root, *lst;
void init() {
tcnt = root = lst = &pl[0];
for(int i=0;i<26;i++)
root->ch[i] = NULL;
root->nxt = root->fa = NULL;
root->mx = 0, root->mnps = INF;
}
sam *newnode(int x = INF) {
tcnt++;
for(int i=0;i<26;i++)
tcnt->ch[i] = NULL;
tcnt->nxt = tcnt->fa = NULL;
tcnt->mx = 0, tcnt->mnps = x;
return tcnt;
}
void add_bin(sam *x) {
x->nxt = bin[x->mx];
bin[x->mx] = x;
}
char s[MAXN + 5];
void clone(sam *x, sam *y) {
for(int i=0;i<26;i++)
x->ch[i] = y->ch[i];
x->fa = y->fa;
}
void sam_extend(int pos, int x) {
sam *cur = newnode(pos), *p = lst;
cur->mx = lst->mx + 1; lst = cur;
add_bin(cur);
while( p && !p->ch[x] )
p->ch[x] = cur, p = p->fa;
if( !p )
cur->fa = root;
else {
sam *q = p->ch[x];
if( p->mx + 1 == q->mx )
cur->fa = q;
else {
sam *nq = newnode();
clone(nq, q);
nq->mx = p->mx + 1;
add_bin(nq);
cur->fa = q->fa = nq;
while( p && p->ch[x] == q )
p->ch[x] = nq, p = p->fa;
}
}
}
void solve() {
init(); scanf("%s", s);
int lens = strlen(s);
for(int i=0;i<lens;i++)
sam_extend(i, s[i]-'a');
for(int i=0;i<lens;i++)
sam_extend(lens+i, s[i]-'a');
for(int i=2*lens;i>=1;i--)
while( bin[i] ) {
bin[i]->fa->mnps = min(bin[i]->fa->mnps, bin[i]->mnps);
bin[i] = bin[i]->nxt;
}
sam *nw = root;
for(int i=0;i<lens;i++) {
for(int j=0;j<26;j++)
if( nw->ch[j] ) {
nw = nw->ch[j];
break;
}
}
printf("%d
", (nw->mnps + 1)%lens + 1);
}
int main() {
int N; scanf("%d", &N);
for(int i=1;i<=N;i++) solve();
}
@details@
注意因为字符串扩展成了两倍,所以数组要相应的开成两倍大。否则会 RE。