如何用 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;
}

浙公网安备 33010602011771号