P5803 [SEERC2019] Tree Permutations 题解
性质结论题。
思路
以下的性质都是在 \(a_i\) 有序的情况下拥有的。
性质一:
有解的必要条件为 \(\forall i\in[1,n-1],a_i\le i\)。
证明:
如果存在 \(a_i>i\),那么点 \(i+1\) 在前 \(i\) 个点合法的前提下无法找到一个父亲,所以无法形成一棵树。
性质二:
在有解的情况下,对于 \(a_i=i,i\in[1,n-1]\),点 \(i\) 必定在 \(1\sim n\) 的路径上。
证明:
如果 \(a_i=i\),那么点 \(i+1\) 及后面的点的父亲节点的编号必然不小于 \(i\),所以一定会经过点 \(i\)。
性质三:
\(1\sim n\) 的路径上的点数不会超过 \(a\) 的种类数。
证明:
显然。
推论:
在满足性质一,性质二的前提下,任意一条合法的 \(1\sim n\) 的路径都可以对应到一个树上。
所以我们就只需要贪心的往路径中添加最小的点,查询最大的点的和就可以了。
时间复杂度:\(O(n\log n)\)。
Code
/*
! 以渺小启程,以伟大结束。
! Created: 2024/06/29 20:55:24
*/
#include <bits/stdc++.h>
using namespace std;
#define fro(i, x, y) for (int i = (x); i <= (y); i++)
#define pre(i, x, y) for (int i = (x); i >= (y); i--)
const int N = 200010;
const int M = 131072;
using i64 = long long;
int n, m, tp, ct, a[N], b[N], v[N];
int sz[M << 1];
i64 vl[M << 1], ans[N];
inline void FAIL() { fro(i, 1, n - 1) cout << -1 << " "; exit(0); }
inline void add(int x, int c) { x += M; while (x) vl[x] += c, sz[x]++, x >>= 1; }
inline void del(int x, int c) { x += M; while (x) vl[x] -= c, sz[x]--, x >>= 1; }
inline auto ask(int p) {
i64 t = 1, res = 0;
while (p != 0) {
if (t > M) res += (t - M) * p, p = 0;
else if (p == sz[t]) res += vl[t], p -= sz[t];
else if (p <= sz[t << 1 | 1]) t = t << 1 | 1;
else res += vl[t << 1 | 1], p -= sz[t << 1 | 1], t = t << 1;
}
return res;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n, m = n * 2 - 2;
fro(i, 1, m) cin >> a[i], b[a[i]] = 1;
sort(a + 1, a + m + 1);
fro(i, 1, m) add(a[i], a[i]);
fro(i, 1, n - 1) if (a[i] > i) FAIL();
fro(i, 1, n - 1) if (a[i] == i) v[i] = 1, ct++, del(i, i);
fill(ans + 1, ans + n, -1);
fro(i, 1, n - 1) {
if (b[i] && v[i] == 0) del(i, i), v[i] = 1, ct++;
ans[ct] = ask(ct);
}
fro(i, 1, n - 1) cout << ans[i] << " \n"[i == n - 1];
return 0;
}