【模板】最小表示法
题目描述
小敏和小燕是一对好朋友。
他们正在玩一种神奇的游戏,叫 Minecraft。
他们现在要做一个由方块构成的长条工艺品。但是方块现在是乱的,而且由于机器的要求,他们只能做到把这个工艺品最左边的方块放到最右边。
他们想,在仅这一个操作下,最漂亮的工艺品能多漂亮。
两个工艺品美观的比较方法是,从头开始比较,如果第 i i i 个位置上方块不一样那么谁的瑕疵度小,那么谁就更漂亮,如果一样那么继续比较第 i + 1 i+1 i+1 个方块。如果全都一样,那么这两个工艺品就一样漂亮。
输入格式
第一行一个整数 n n n,代表方块的数目。
第二行 n n n 个整数,每个整数按从左到右的顺序输出方块瑕疵度的值。
输出格式
一行 n n n 个整数,代表最美观工艺品从左到右瑕疵度的值。
样例 #1
样例输入 #1
10
10 9 8 7 6 5 4 3 2 1
样例输出 #1
1 10 9 8 7 6 5 4 3 2
提示
- 对于 20 % 20\% 20% 的数据, n ≤ 1000 n\le 1000 n≤1000;
- 对于 40 % 40\% 40% 的数据, n ≤ 1 0 4 n\le 10^4 n≤104;
- 对于 100 % 100\% 100% 的数据, n ≤ 3 × 1 0 5 n\le 3\times 10^5 n≤3×105。
题意:
虽然题目给定的是个数组,但我们可以把问题抽象成:给定一个长度为 n 的字符串 s,找出字典序最小的循环移位(长度也为 n)。以下的分析我们均用 “串” 来代替数组。
思路:
SAM 高度压缩了原串各种长度的所有子串。我们发现:字符串 s + s 包含 s 的所有循环移位作为子串。所以如果要找字典序的最小循环移位,不妨将原串复制一份,形成一个长度为 2n 的串,选择所有长度为 n 的子串集合中字典序最小的那个。
我们对长度为 2n 的新串构建后缀自动机,从DAG的根节点开始,每次贪心的走字典序最小的节点,走 n 步,边走边输出即可。
时间复杂度: O ( n ) O(n) O(n)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 6e5 + 10, M = N << 1;
int n, a[N], len[M], fa[M], np = 1, tot = 1;
map<int, int> ch[M];
vector<int> g[M];
void extend(int c)
{
int p = np; np = ++tot;
len[np] = len[p] + 1;
while (p && !ch[p][c]) {
ch[p][c] = np;
p = fa[p];
}
if (!p) {
fa[np] = 1;
}
else {
int q = ch[p][c];
if (len[q] == len[p] + 1) {
fa[np] = q;
}
else {
int nq = ++tot;
len[nq] = len[p] + 1;
fa[nq] = fa[q], fa[q] = fa[np] = nq;
while (p && ch[p][c] == q) {
ch[p][c] = nq;
p = fa[p];
}
ch[nq] = ch[q];
}
}
}
signed main()
{
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
scanf("%d", &a[i]);
a[i + n] = a[i];
}
n <<= 1;
for (int i = 0; i < n; ++i) {
extend(a[i]);
}
int p = 1;
for (int i = 0; i < n / 2; ++i) {
auto pp = ch[p].begin(); //由于map自动按第一关键字排序,因此每次贪心地选择首元素走就行
int ele = (*pp).first;
int nd = (*pp).second;
printf("%d ", ele);
p = nd;
}
puts("");
return 0;
}