CF2171F Rae Taylor and Trees (hard version)
CF2171F Rae Taylor and Trees (hard version)
Problem
“竟然有平民敢想和我坐在一起。要认清你的身份!”
—— Claire François
这是该问题的困难版本。困难版与简单版的区别在于,困难版要求你构造一个满足条件的树。
作为大地魔法师,Rae 精通种植树木的魔法!但 Manaria 吹嘘自己能种出更稀有的树。Rae 记得,最罕见的树可以用一个特定的排列来生成,请你帮她构造出来!
给定一个长度为 \(n\) 的排列 \(p\)。
请判断是否存在一棵有 \(n\) 个结点(编号为 \(1,2,\dots,n\))的无向树,满足如下条件:
- 对于所有直接相连的一对结点 \(u\)、\(v\)(\(1 \leq u < v \leq n\)),\(u\) 必须在排列 \(p\) 中出现在 \(v\) 前面。
如果存在,构造出任意一棵符合条件的树。
\(^*\)一个长度为 \(n\) 的排列是一个恰好包含 \(1\) 到 \(n\) 每个整数各一次的序列,顺序任意。
Thinking
题目还是有一点迷惑性,首先想到的拓扑排序,但发现做不了
很显然是求对于 \(u < v\) && \(p_u < p_v\) 的对进行选择,看是否构成树
满足\(u < v\) 是容易的,于是考虑维护 \(p_u < p_v\),应该是可以用SET维护的
考虑减少信息,想到最小值和最大值拿来连边是不劣的
用前缀最小值和后缀最大值维护一下
Solution
最小值和最大值一定是一个局部的东西,在局部之内连边,然后在整体上把很多给块连起来
发现其实写递归是很好实现的
定义 solve(r) 为右边界为R的前缀是否合法
-
边界:R = 1时返回1
-
块内操作:找到当前前缀中的最小值(预处理)
然后把最小值后面的连边
-
连接两个块,以最小值的位置为分界线
判断前一个块的最小值(前缀最小值)是否小于于后面块的最大值(后缀最大值)
小于则继续递归
solve(分界线-1)否则返回0
Code
#include <bits/stdc++.h> using namespace std; const int N = 2e5 + 10; int a[N], s1[N], s2[N], n, pos[N]; vector <pair <int, int>> ans; bool check(int r) { if (r == 1) return 1; int mi = s1[r], p = pos[mi]; for (int i = p + 1; i <= r; i++) ans.emplace_back(mi, a[i]); if (p == 1) return 1; if (s1[p - 1] <= s2[p]) ans.emplace_back(s1[p - 1], s2[p]); else return 0; return check(p - 1); } inline void solve() { cin >> n; for (int i = 1; i <= n; i++) cin >> a[i], pos[a[i]] = i; s1[0] = INT_MAX; for (int i = 1; i <= n; i++) s1[i] = min(s1[i - 1], a[i]); s2[n + 1] = 0; for (int i = n; i >= 1; i--) s2[i] = max(s2[i + 1], a[i]); ans.clear(); if (check(n)) { cout << "Yes\n"; for (auto [u, v] : ans) cout << u << ' ' << v << '\n'; } else cout << "No\n"; } int main() { cin.tie(nullptr) -> ios::sync_with_stdio(0); int t; cin >> t; while (t--) solve(); return 0; }

浙公网安备 33010602011771号