CF2124I Lexicographic Partition 题解

Description

给定一个数组 aa,定义函数 f(a)f(a) 如下:

kk 是一个满足 1kn1 \leq k \leq n 的整数。
将数组 aa 分成 kk 个子数组 s1,s2,,sks_1, s_2, \ldots, s_k,使得 s1+s2++sk=as_1 + s_2 + \cdots + s_k = a,这里的 ++ 表示数组连接操作。

构造一个新的数组 bb,初始为空。对于每个 i=1i = 1kk,将子数组 sis_i最小值添加到 bb 的末尾。

在所有可能的 kk 和划分方式中,定义 f(a)f(a) 为使得生成的 bb 字典序最大的那个 kk

现在给定一个长度为 nn 的整数序列 x1,x2,,xnx_1, x_2, \ldots, x_n,请判断是否存在一个排列 aa,使得对于每个 1in1 \leq i \leq n,都有 f([a1,a2,,ai])=xif([a_1, a_2, \ldots, a_i]) = x_i

如果存在这样的排列 aa,请输出其中一种可能的结果(如果有多种,任选一种输出即可)。

n2×105n\leq 2\times 10^5

Solution

首先考虑给定 aa 数组,怎么求这个数组的函数值。

考虑从前往后确定每个区间的左右端点,假设当前左端点在 ii,那么右端点 jj 一定需要满足 ai,ai+1,,aja_i,a_{i+1},\ldots,a_j 都大于等于 aia_i,否则从这一位就不优。

同时需要让下一位尽可能优,就一定要最大化 aj+1a_{j+1},设 kk 为最大的 kk 使得 aiaka_i\sim a_k 都大于等于 aia_i,则下一个区间的左端点为 aiaka_i\sim a_k 的最大值。如果 k=ik=i,则这个左端点是 i+1i+1

回到原问题。

容易发现这个跳的过程类似于给每个点找到唯一的父亲,显然 ii 的父亲是 ii 左边第一个 xj=xi1x_j=x_i-1jj,因为跳前 xi1x_i-1 步时每次肯定是能往后跳就尽量往后跳,所以第 xi1x_i-1 步是一定跳到了 jj,最后一步为 ii。如果找不到这样的 jj 就无解。

然后有个引理,是一个合法序列 xx 建出来的树,一定满足每个子树构成一段区间。证明就考虑对于一个节点 xx,如果已经证明了 xx 的子树是一个区间,然后去归纳证明 xx 的每个儿子的子树也是区间。

具体地,如果 xx 只有 11 个儿子,则这个儿子一定是 x+1x+1,显然还是区间。如果有大于等于 22 个儿子,那么这些儿子的权值一定按升序排列,如果存在一条 uvu\to vuuvv 对应的祖先不相同,则根据能往后跳就往后跳的原则,前面从 xx 开始时一定是先跳 xx 的儿子,再进入子树区间,不再出来。这里就矛盾了。

同时还有个无解条件是如果存在一个点有至少两个儿子,且存在一个儿子又有至少两个儿子。这个形式可以看成存在一个 [1,2,2,3,3][1,2,2,3,3] 的子序列,显然 a1<a2<a3a_1<a_2<a_3a3<a4<a5a_3<a_4<a_5,这两个不等式可以联立起来,导致 4,54,5 可以被 11 跳到。

可以证明如果满足上述所有的条件,就一定有解。


构造就考虑对于子树递归构造,让每个子树的值域也构成一段区间。

设当前节点为 xx,值域区间为 [l,r][l,r]

这里钦定如果 xx 只有一个儿子,就让 axa_x 是子树最大值,否则让 xx 是子树最小值。

首先如果 xx 只有一个儿子,那么这个儿子一定是 x+1x+1,递归儿子的子树,同时把值域区间设为 [l,r1][l,r-1] 即可。

如果 xx 有大于等于两个儿子,就按照儿子的编号大小,给儿子 yy 分配一个大小为 szysz_y 的区间。根据引理,yy 只有至多一个儿子,所以 aya_y 一定是子树最大值,这样就巧妙地保证了 yy 子树里除去 yy 的节点一定不能被 xx 一步跳到。

时间复杂度:O(n)O(n)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 2e5 + 5;

int n;
int x[kMaxN], sz[kMaxN], mx[kMaxN], res[kMaxN];
std::vector<int> G[kMaxN];

bool solve(int l, int r, int a, int b) {
if (l == r) return res[l] = a, 1;
if (G[l].size() == 1) {
res[l] = b;
return solve(l + 1, r, a, b - 1);
} else {
for (auto i : G[l])
if (G[i].size() > 1)
return 0;
res[l] = a++;
for (auto i : G[l]) {
if (!solve(i, i + sz[i] - 1, a, a + sz[i] - 1)) return 0;
a += sz[i];
}
return 1;
}
}

void dickdreamer() {
static int lst[kMaxN];
std::cin >> n;
for (int i = 0; i <= n; ++i) G[i].clear(), res[i] = lst[i] = 0;
for (int i = 1; i <= n; ++i) std::cin >> x[i];
lst[x[1]] = 1;
for (int i = 2; i <= n; ++i) {
if (!lst[x[i] - 1]) return void(std::cout << "NO\n");
G[lst[x[i] - 1]].emplace_back(i);
lst[x[i]] = i;
}
for (int i = n; i; --i) {
sz[i] = 1, mx[i] = i;
for (auto j : G[i]) sz[i] += sz[j], mx[i] = std::max(mx[i], mx[j]);
if (mx[i] - i + 1 != sz[i]) return void(std::cout << "NO\n");
}
if (!solve(1, n, 1, n)) return void(std::cout << "NO\n");
std::cout << "YES\n";
for (int i = 1; i <= n; ++i) std::cout << res[i] << " \n"[i == n];
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

CF2124I Lexicographic Partition 题解
https://sobaliuziao.github.io/2025/07/21/post/54d92b1f.html
作者
Egg_laying_master
发布于
2025年7月21日
许可协议