Ybt 金牌导航 6.2.F 大根堆 / BZOJ 4919 大根堆(LIS,启发式合并)

题意简述

有一个以 1 为根的有根树,每个点有权值 vi。你需要选出一个点集 S,使得点集里任意两个元素 x,y,若 x 在原树上是 y 的祖先,则要满足 vx>vy。求选出的点集的最大大小是多少。

解法

原题限制相当于:在选出的点集构成的新树 T 中,每个点到根节点上的点权形成上升子序列。要让点集大小最大,就是让上升子序列长度最大。

不会最长上升子序列的建议前往这里

做法:对每个节点维护一个 set,合并子树的 set 时用启发式合并,插入元素时寻找第一个大于等于它本身的元素,删除原来的元素,并加入这个元素。若没有,直接加入。最终根节点的 set 大小就是答案。

这个做法为什么是对的?

这里 set 里维护的数相当于某个 LIS 上,长度为某值时该节点作为 LIS 末尾的元素值最小。换句话说,set 内的每个元素维护的是一条 LIS,该元素为 LIS 的末尾。

首先,求 LIS 的一个经典贪心结论:长度一定时,将末尾元素替换为更小的元素肯定不劣。(这样更有利于我们之后扩展该 LIS 的长度)

插入元素时:

  • 若它是最大的元素,直接插入显然没有问题。
  • 若它不是最大的元素,找到第一个大于等于它的数,该数对应的 LIS 的倒数第二个元素一定没有要插入的元素大,根据求 LIS 的经典贪心结论,将该 LIS 的末尾元素替换成药插入的元素显然不劣。

时间复杂度 O(nlog2n)

代码略去缺省源。

点击查看代码
int n,m,a[maxn];
vector<int>G[maxn];
multiset<int>s[maxn];
void merge(multiset<int>&x,multiset<int>&y){
	if(x.size()<y.size())swap(x,y);
	while(!y.empty())x.insert(*y.begin()),y.erase(y.begin());
}
void dfs(int x){
	for(int u:G[x]){
		dfs(u);merge(s[x],s[u]);
	}
	auto it=s[x].lower_bound(a[x]);
	if(it!=s[x].end())s[x].erase(it);
	s[x].insert(a[x]);
}
void solve_the_problem(){
	n=rd(),a[0]=inf;
	rep(i,1,n){
		int x;a[i]=rd(),x=rd();
		G[x].pb(i);
	}
	dfs(1);
	write(s[1].size());
}

作者:dcytrl

出处:https://www.cnblogs.com/dcytrl/p/17981210

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   dcytrl  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示