如何用 Lyndon 无脑构造

QOJ9729 Dividing Sequence

给定序列 \(a\),把它划分成两个子序列 \(b,c\),要求字典序 \(b\le c\),求字典序最小的 \(c\)

字典序问题,可以在 Lyndon 分解上考虑,设分解得到 \(s=w_1+\dots+w_k\)

考虑第一个字符分给谁,如果分给 \(b\) 的话,那 \(c\) 只能以 \(w_1\) 的一个后缀开头,

而这样显然不优(令 \(b=\varnothing,c=a\) 就可以使 \(c\)\(w_1\) 开头),所以第一个字符分给 \(c\)

可以发现 \(c\) 至少要取完 \(w_1\),而且取的越少越好(因为后面的 Lyndon 串字典序更小),

所以不妨先把 \(w_1\) 分给 \(c\),可以发现若 \(w_1>w_2\) 直接把后面所有字符分给 \(b\) 即可,

\(w_1=w_2\),可以发现 \(b\) 至少要取完 \(w_2\),规约到 \(w_3+\dots+w_k\) 的子问题,继续这样做即可。

原题 \(n\le 5000\),可能爆标了?

#include <cstdio>
#include <cstring>
int T, n, a[5050], w[5050], p[5050], Z[5050];
int main()
{
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i)
            scanf("%d", a + i);
        int i = 1, j, k, c = 0, d = 0, o = 0;
        while (i <= n)
        {
            j = i + 1, k = i;
            while (j <= n)
            {
                if (a[j] == a[k])
                    ++j, ++k;
                else if (a[j] > a[k])
                    ++j, k = i;
                else
                    break;
            }
            ++d;
            while (i <= k)
                w[++c] = i, p[c] = d, i += j - k;
        }
        w[c + 1] = n + 1, p[c + 1] = -1;
        for (int i = 1; i <= c; i += 2)
        {
            for (int j = w[i]; j < w[i + 1]; ++j)
                Z[++o] = a[j];
            if (p[i] != p[i + 1])
                break;
        }
        printf("%d\n", o);
        for (int i = 1; i <= o; ++i)
            printf("%d ", Z[i]);
        puts("");
    }
    return 0;
}
posted @ 2025-02-05 07:59  Jijidawang  阅读(97)  评论(5)    收藏  举报