题解:P5803 [SEERC 2019] Tree Permutations

posted on 2025-02-22 00:12:01 | under | source

题意:有一个 \(n\) 个点的树,根为 \(1\)。记 \(p_i<i\)\(i\) 的父亲,\(w_i\) 为父亲边权。现将 \(p,w\) 随机打乱得到 \(a\),对每个 \(k\in [1,n)\),当 \(1\to n\) 路径上有 \(k\) 条边,输出最长长度或报告无解。\(n\le 10^5\)

\(a\) 排序。首先初始思路是找出 \(x_1\dots x_k\),其中 \(x_1=1,x_i<x_{i+1}\),也就是 \(1\to n\) 的链,然后在剩下的元素中贪心选取最小的构成 \(p\)。那么问题是怎么确定 \(x\)

尝试观察性质,不断强化条件,从而推出正解:

  1. \(\forall i,a_{i}<i\) 否则无解。显然。
  2. \(a_i=i\),则 \(i\) 一定在 \(1\to n\) 路径上。证明:对于 \(j\in [2,i]\),只能选取 \(a_1\dots a_{i-1}\) 作为父亲节点,所以 \(j\in [i+1,n]\) 无法一步跳到 \([1,i-1]\),所以必然经过 \(i\)
  3. \(k=\sum [a_i=i]\) 时一定有解。证明:首先让他们构成 \(1\to n\) 的链,然后考虑剩下的点,对于 \(j\in (x_i,x_{i+1})\),都有 \(a_j<j\),所以一定有解。构造方案即为选取剩下的 \(a\) 中最小的那些。
  4. \(mx\)\(a\) 不同元素个数,则有 \(k\le mx\)。显然。
  5. 对于 \(\sum[a_i=i]\le k\le mx\) 均有解。证明:增量法,每次选取剩下元素中最小且与 \(x\) 不同的元素加入 \(1\to n\) 链上,则剩下的节点一定有相应的 \(p\) 可匹配。因为考虑拿走的元素 \(y\),设 \(q\)\(y\) 此前对应的 \(p\),设拿走 \(y\) 后节点 \(z\) 失去对应 \(p\),由于 \(q<y<z\) 所以让 \(z\)\(q\) 匹配即可。

综上,恰在区间内的 \(k\) 才有解。每次删去新增元素,并选取剩下的前 \(k\) 大作为链边权即可。可以做到 \(O(n)\),但我偷懒用线段树 \(O(n\log n)\)

代码

#include<bits/stdc++.h>
using namespace std;

#define int long long
const int N = 2e5 + 5;
int n, m, a[N], buc[N], cnt;
bool vis[N];
vector<int> del; 

namespace Sg_Tree{
	#define lt (u << 1)
	#define rt (u << 1 | 1)
	#define mid (l + r >> 1)
	int t[N << 2], s[N << 2];
	
	inline void psup(int u) {t[u] = t[lt] + t[rt], s[u] = s[lt] + s[rt];}
	inline void upd(int u, int l, int r, int k, int p){
		if(l == r) {t[u] += k * p, s[u] += p; return ;}
		if(k <= mid) upd(lt, l, mid, k, p);
		else upd(rt, mid + 1, r, k, p);
		psup(u);
	}
	inline int fid(int u, int l, int r, int k){
		if(l == r) return l * k;
		if(s[rt] <= k) return t[rt] + fid(lt, l, mid, k - s[rt]);
		return fid(rt, mid + 1, r, k);
	}
}using namespace Sg_Tree;
signed main(){
	cin >> n, m = 2 * n - 2;
	for(int i = 1; i <= m; ++i) scanf("%d", &a[i]), ++buc[a[i]], upd(1, 1, n, a[i], 1);
	sort(a + 1, a + 1 + m);
	for(int i = 1; i <= m; ++i){
		if(a[i] > i) {for(int i = 1; i < n; ++i) printf("-1 "); return 0;}
		if(a[i] == i){
			vis[i] = true;
			--buc[a[i]], upd(1, 1, n, a[i], -1), ++cnt;
		}
	}
	for(int i = 1; i < cnt; ++i) printf("-1 ");
	if(cnt) printf("%lld ", fid(1, 1, n, cnt));
	for(int i = 1; i < n; ++i) 
		if(buc[i] && !vis[i]){
			upd(1, 1, n, i, -1), ++cnt;
			printf("%lld ", fid(1, 1, n, cnt));
		}
	for(int i = cnt + 1; i < n; ++i) printf("-1 ");
	return 0;
}
posted @ 2026-01-15 08:18  Zwi  阅读(2)  评论(0)    收藏  举报