zoukankan      html  css  js  c++  java
  • 「JSOI 2019」节日庆典

    题目

    点这里看题目。简要题意如下:

    给定一个字符串 \(S\),对它的每一个前缀,求其最小表示对应的后缀的下标。

    对于 \(100\%\) 的数据,满足 \(1\le |S|\le 3\times 10^6\)

    分析

    显然,一个循环表示就是一个后缀拼上一个前缀。

    容易想到这样一个算法:基于询问的递推关系,我们可以先通过后缀来筛选出备选答案。具体来说,对于前缀 \(S[1:k]\),维护 \(P_k\) 为所有极小后缀的集合。如果一个后缀 \(u\)\(S[1:k]\) 中不是极小的,就说明存在另一个后缀 \(v\),使得 \(u>v\)\(v\) 不是 \(u\) 的前缀。换言之,\(u\) 已经被保证不可能成为最优解了。

    考虑从 \(P_k\) 递推到 \(P_{k+1}\),我们只需要考虑 \(P_k\) 中的元素在末尾添上 \(S[k+1]\) 后的大小关系,以及 \(S[k+1]\) 这个后缀的影响。根据 \(P_k\) 的构成,我们只需要考虑较短的后缀对于较长的后缀影响即可。

    在需要回答问题的时候,我们枚举 \(P_k\) 中的后缀,并取对应循环表示的最小值。此时我们需要做的比较都是某个后缀和整个串比较,用扩展 KMP 预处理 LCP 之后即可快速完成。

    现在这个算法仍然是 \(O(n^2)\) 的,用一个 aaa...aaa 就可以导致 \(|P_k|=k\),复杂度爆炸。不过,在 aaa...aaa 这种情况下,\(P_k\) 其实包含了一大堆无效的后缀,我们得想办法把它们丢掉。

    再回顾一下 \(P_k\) 的构成:原始定义中,如果 \(u,v\) 都包含在了 \(P_k\) 里面,且 \(|u|>|v|\),那么一定有 \(v\)\(u\) 的 Border。而如果熟悉 Border 的构成,我们就不难想到透过 Border 的等差结构来得到下面这个结论:

    \(P_k\) 中只需要包含 \(O(\log k)\) 个后缀。


    考虑其中的任意两个后缀 \(u,v\),我们可以说明当 \(2|v|>|u|>|v|\)\(v\) 这个后缀是无效的。

    道理很简单,由于 \(v\)\(u\) 的 Border,所以应该有 \(u=AAB,v=AB\)。考虑 \(w=B\),我们可以发现 \(v\) 被夹在 \(u,w\) 之间,并且怎么也不可能被取到:设 \(c=S[1]\),如果有 \(uc>vc\) ,那么也就有 \(vc>wc\);反过来如果有 \(uc<vc\),那么也就有 \(vc<wc\)

    所以我们完全可以筛去 \(v\)。最终不会存在满足 \(2|v|>|u|>|v|\) 的一对后缀,因此 \(P_k\) 中只会包含 \(O(\log k)\) 个元素。

    这个结论的分析过程其实也告诉了我们该怎么去维护 \(P_k\),按照这个做法即可得到 \(O(n\log n)\) 的算法。

    小结:

    1. 处理字符串(序列)问题时,对于这种递推结构要敏感一点,并且应当相应地想到递推的算法
    2. \(P_k\) 的缩减其实也是来自于 Border 的循环结构,由同一循环生成的 Border 在很多情况下都是可以被直接略过的。当然这也需要对于 Border 的足够熟悉;
    3. 注意一下关于 LCP 的算法在字符串比较中的应用。

    代码

    #include <cstdio>
    #include <cstring>
    
    #define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
    #define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
    
    const int MAXN = 3e6 + 5;
    
    template<typename _T>
    void read( _T &x ) {
        x = 0; char s = getchar(); bool f = false;
        while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
        while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
        if( f ) x = -x;
    }
    
    template<typename _T>
    void write( _T x ) {
        if( x < 0 ) putchar( '-' ), x = -x;
        if( 9 < x ) write( x / 10 );
        putchar( x % 10 + '0' );
    }
    
    template<typename _T>
    _T MIN( const _T a, const _T b ) {
        return a < b ? a : b;
    }
    
    int tmp[MAXN], tot;
    int stk[MAXN], top;
    
    int z[MAXN];
    char str[MAXN];
    
    int N;
    
    int main() {
        scanf( "%s", str + 1 );
        N = strlen( str + 1 ), z[1] = N;
        for( int i = 2, l = 1, r = 1 ; i <= N ; i ++ ) {
            if( i < r ) z[i] = MIN( r - i, z[i - l + 1] );
            while( i + z[i] <= N && str[1 + z[i]] == str[i + z[i]] ) z[i] ++;
            if( i + z[i] > r ) l = i, r = i + z[i];
        }
        rep( i, 1, N ) {
            tmp[tot = 1] = i;
            per( j, top, 1 ) {
                int u = stk[j], v = tmp[tot];
                if( str[u + i - v] > str[i] ) continue;
                if( str[u + i - v] < str[i] ) tot --;
                tmp[++ tot] = u;
            }
            stk[top = 1] = tmp[tot];
            per( j, tot - 1, 1 ) {
                int lu = i - stk[top] + 1,
                    lv = i - tmp[j] + 1;
                if( ( lv << 1 ) <= lu )
                    stk[++ top] = tmp[j];
            }
            int ans = stk[1];
            rep( j, 2, top ) {
                int u = ans, v = stk[j];
                int p = u + i - v + 1;
                if( z[p] < v - u )
                    ans = str[p + z[p]] <= str[1 + z[p]] ? u : v;
                else {
                    p = 1 + v - u;
                    if( z[p] < u - 1 )
                        ans = str[1 + z[p]] <= str[p + z[p]] ? u : v;
                    else ans = u;
                }
            }
            write( ans ), putchar( i == N ? '\n' : ' ' );
        }
        return 0;
    }
    
  • 相关阅读:
    2020 牛客 NOIP 赛前集训营 提高级(第四场) B-色球 平衡树模板
    P4084 [USACO17DEC]Barn Painting G
    CSP-S 2020
    CQOI 2020省选
    我回来了
    hdu3605(二分图多重匹配伪模板)
    舞动的夜晚(二分图的必须边和可行边)
    poj3436(最大流+拆点)
    P2954([USACO09OPEN]移动牛棚Grazing2,dp)
    CSP-S 2020 游记
  • 原文地址:https://www.cnblogs.com/crashed/p/15781018.html
Copyright © 2011-2022 走看看