@poj - 1509@ Glass Beads


@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\n", (nw->mnps + 1)%lens + 1);
}
int main() {
	int N; scanf("%d", &N);
	for(int i=1;i<=N;i++) solve();
}

@details@

注意因为字符串扩展成了两倍,所以数组要相应的开成两倍大。否则会 RE。

posted @ 2019-01-10 20:36  Tiw_Air_OAO  阅读(97)  评论(0编辑  收藏  举报