@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。

转载于:https://www.cnblogs.com/Tiw-Air-OAO/p/10252268.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值