hdu 5442 Favorite Donut (最小表示法 or 后缀数组)

本文介绍了解决循环最小字符串问题的两种方法:最小表示法算法和后缀数组算法。最小表示法算法是一种简洁高效的O(n)算法,适用于求解字符串的最小表示。后缀数组算法通过构造特殊的字符串并求解后缀数组和最长公共前缀来找到循环最小字符串。

题意:
给一个字符串,正向找一遍循环最小,逆向找一遍循环最小。
然后把两个最小根据下标关系,取一个答案。。
思路:
这道题用 后缀数组 很好解决。
但是,还有个更适合的方法,最小表示法算法。
最小表示法 - 周源 PPT

这是一个非常简洁的 O(n) 算法,可以求一个字符串的最小 or 最大表示。

int maxPresent(const char s[], int len) {
    int i = 0, j = 1, k = 0, t;
    while ( i < len && j < len && k < len ) {
        t = s[ (i + k) % len ] - s[ (j + k) % len ];
        if ( !t ) ++ k;
        else {
            // 比较失败时,移动指针,k 置 0
            // 求最小表示,移动大的一方
            // 求最大表示, 移动小的一方
            if ( t < 0 ) i += k + 1;
            else j += k + 1;
            if ( i == j ) ++ j;
            k = 0;
        }
    }
    return min(i, j);
}

后缀数组 ver1
扩成2*n-1,正反两遍求sa,lcp,遍历sa

char s[N+5], rs[N+5];

int main() {
    int t;
    scanf("%d", &t);
    while ( t -- ) {
        int n;
        scanf("%d%s", &n, s);
        reverse_copy(s, s + n, rs);
        string cw = s;
        cw += s; cw.erase(cw.end() - 1);
        string ccw = rs;
        ccw += rs; ccw.erase(ccw.end() - 1);

        int idx1 = -1, idx2 = -1;

        SA::construct_sa(cw, cw.length(), 1);
        for (int i = cw.length(); i >= 1; -- i) {
            if ( SA::sa[i] < n ) {
                int j = i;
                idx1 = SA::sa[i];
                while ( j && SA::lcp[j-1] >= n ) {
                    -- j;
                    idx1 = min ( idx1, SA::sa[j] );
                }
                break;
            }
        }

        SA::construct_sa(ccw, ccw.length(), 1);
        for (int i = ccw.length(); i >= 1; -- i) {
            if ( SA::sa[i] < n ) {
                int j = i;
                idx2 = SA::sa[i];
                while ( j && SA::lcp[j-1] >= n ) {
                    -- j;
                    idx2 = max ( idx2, SA::sa[j] );
                }
                break;
            }
        }

        string sub1 = cw.substr(idx1, n), sub2 = ccw.substr(idx2, n);
        int res = sub1.compare(sub2);
        idx2 = n - 1 - idx2;
        if ( res < 0 ) {
            printf("%d 1\n", idx2 + 1);
        } else if ( res > 0 ) {
            printf("%d 0\n", idx1 + 1);
        } else {
            if ( idx2 < idx1 ) {
                printf("%d 1\n", idx2 + 1);
            } else
                printf("%d 0\n", idx1 + 1);
        }
    }
    return 0;
}

后缀数组 Ver2
用一个特殊字符把正反扩充后的串连起来,求一遍sa,和lcp。
然后扫一遍求答案。。
虽然简洁一些,但是慢许多

char s[N+5], rs[N+5];

pair<int, int> trans(int idx, int n) {
    if ( idx > n )  {
        idx -= 2 * n;
        idx = n - 1 - idx;
        return make_pair( idx, 1 );
    } else
        return make_pair( idx, 0 );
}

int main() {
    int t;
    scanf("%d", &t);
    while ( t -- ) {
        int n;
        scanf("%d%s", &n, s);
        reverse_copy(s, s + n, rs);
        string cw = s;
        cw += s; cw.erase(cw.end() - 1);
        string ccw = rs;
        ccw += rs; ccw.erase(ccw.end() - 1);
        string ss = cw + '$' + ccw;

        SA::construct_sa(ss, ss.length(), 1);

        pair<int, int> ans = make_pair(INT_MAX, INT_MAX);
        for (int i = ss.length(); i >= 1; -- i) {
            if ( SA::sa[i] < n || 2 * n <= SA::sa[i] && SA::sa[i] <= 3 * n - 1 ) {
                ans = trans( SA::sa[i], n );
                int j = i;
                while ( j && SA::lcp[j-1] >= n ) {
                    -- j;
                    ans = min ( ans, trans( SA::sa[j], n ) );
                }
                break;
            }
        }
        printf("%d %d\n", ans.first + 1, ans.second);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值