洛谷P8098 「USACO 2022.1 Gold」Tests for Haybales
洛谷P8098 「USACO 2022.1 Gold」Tests for Haybales
题目大意
根据某个有序的整数数组 \(a_1\leq a_2\leq \dots \leq a_n\) 和正整数 \(k\),我们这样定义它对应的 \(b\) 数组:\(b_i\) 表示最大的 \(j\),使得 \(a_j \leq a_i + k\)。显然有:\(i\leq b_i\) 且 \(b_1 \leq b_2\leq \dots\leq b_n\leq n\)。
现在给出 \(b\) 数组,请你构造出符合条件的 \(a\) 数组和整数 \(k\)。要求:\(0\leq a_i\leq 10^{18}\),\(1\leq k\leq 10^{18}\)。
数据范围:\(1\leq n\leq 10^5\)。
本题题解
假设 \(k\) 已知。那么题目要求的 \(a\) 数组可以概括为如下三个条件:
- \(\forall i \in [1, n): a_{i}\leq a_{i + 1}\)。
- \(\forall i\in[1, n]: a_{b_i} - a_{i}\leq k\)。
- \(\forall i\in [1, n]: a_{b_{i} + 1} - a_{i} > k\),其中 \(a_{n + 1}\) 可以认为是无穷大。
暴力做法是,直接指定一个较大的 \(k\),然后跑差分约束。但是 SPFA 太慢了,这样只能获得 \(50\) 分。
我们考虑更高明的构造解法。从每个 \(i\) 向 \(b_{i} + 1\) 连一条边,可以得到一棵 \(n + 1\) 个节点的树。并且由于 \(b\) 数组单调不下降和 \(b_{i} \geq i\) 的特性,这棵树是很有特点的:假设把每个节点的儿子都按编号从小到大排好序,那么从最底层开始,逐层往上,依次写下每个节点的编号,得到的序列刚好是 \(1, 2, 3, \dots, n + 1\)。
一个直觉的想法是,从最底层开始,依次把每个节点的 \(a_i\) 赋为 \(0, 1, 2, 3\dots\),如果这个节点有孩子,就把它的 \(a_{i}\) 扩大为它编号最大的孩子的 \(a_{i}\) 值加 \(k + 1\)(后面节点的 \(a_i\) 也跟着扩大)。这样可以满足 \(a\) 序列单调不降,也可以满足 \(a_{b_{i} + 1} - a_{i} > k\)(因为 \(b_{i} + 1\) 就是 \(i\) 的父亲,以下也称为 \(\mathrm{fa}(i)\))。但第 2 个条件不一定满足。于是现在我们面临的难点是,在保证 \(a_{i} + k\) 小于 \(a_{\mathrm{fa}(i)}\) 的同时,要让 \(a_{i} + k\) 大于等于 \(\mathrm{fa}(i)\) 同一层的,所有在 \(\text{fa}(i)\) 左边的节点 \(l\) 的 \(a_l\) 值。换句话说,我的儿子,只有我自己能管,和我同辈的弟弟妹妹们管不了。你可以在纸上画一画,尝试通过一些微调解决这个问题(如把 \(a_{i}\) 的值增加一点,把 \(a_l\) 的值减小一点,等等),但会发现这很难。
注意到 \(k\) 可以很大,所以下面我们把格局打开,不要微调了,回到问题本质,看有没有简单粗暴的方式能解决它。我们从下到上定义树的深度:设最底层的节点 \(d_{i} = 0\),每一层节点的 \(d_i\) 是它们儿子的 \(d_i + 1\)。因为 \(b_i + 1\) 是 \(i\) 的父亲,它永远恰好在 \(i\) 的上一层,我们不妨令 \(a_i = d_i \cdot k + t_i\)。于是问题转化为,给每个节点安排一个 \(t_i\),使它满足如下条件:
- \(t_{\mathrm{fa}(i)} > t_i\)。
- 对于 \(\mathrm{fa}(i)\) 同一层左边的所有节点 \(l\),\(t_l \leq t_i\)。
- 同一层里的两个节点,左边的 \(t_i\) 较小(或者说编号小的节点 \(t_i\) 较小)。
当然,还需要 \(0\leq t_i\leq k\),不过鉴于 \(k\) 可以很大,所以这个要求不会限制我们构造 \(t\) 数组。
经过这样的转化,节点和节点之间直接是 \(t\) 的大小比较,而不再受 \(k\) 影响了。你会发现,\(t\) 数组的条件并不难满足,甚至可以有很多种不同的构造方法。比如说,我们 dfs 整棵树,维护一个全局的计数器,在离开一个节点 \(i\) 时,令计数器数值加 \(1\),并将此值记为 \(t_i\)。在 dfs 到节点 \(i\) 时,显然已经访问过并且离开了 \(\mathrm{fa}(i)\) 左边的所有节点,并且还尚未离开 \(\mathrm{fa}(i)\)。因为每个节点的所有儿子已经按编号从小到大排好序,我们这样 dfs 时,同一层的节点自然先经过编号小的。综上所述,这样构造的 \(t\) 数组满足所有要求。
因为 \(t_i\) 的取值范围是 \([1, n]\),令 \(k = n\) 即可(或更大也无妨)。
时间复杂度 \(\mathcal{O}(n)\)。
最后我们来反思一下,为什么一开始在两层之间构造,微调来微调去,很难解决问题;一旦想到了构造这个 \(t\) 数组,问题就很容易了呢?因为我们抓住了 \(k\) 可以很大的特性,从一个更宏观的视角去看问题。不纠结于两层之间,而是整个数组一起构造。我们用一个范围在 \([1, n]\) 的,类似 dfs 序的东西,去解决两层之间数值大小关系,可能会产生很多“间隙”或者“浪费”,但是不要紧。我们非常优雅、漂亮地给这个问题来了个“降维打击”。
参考代码
// problem: P8098
#include <bits/stdc++.h>
using namespace std;
#define mk make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
const int MAXN = 1e5;
int n, jump[MAXN + 5];
vector<int> G[MAXN + 5];
int dep[MAXN + 5];
ll ans[MAXN + 5], K;
int idx = 0;
void dfs(int u) {
for (int i = 0; i < SZ(G[u]); ++i) {
int v = G[u][i];
dep[v] = dep[u] + 1;
dfs(v);
}
ans[u] = K * (n - dep[u]) + (++idx);
}
int main() {
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> jump[i];
G[jump[i] + 1].push_back(i);
}
K = n;
dfs(n + 1);
cout << K << endl;
for (int i = 1; i <= n; ++i) {
cout << ans[i] << endl;
}
return 0;
}