狗狗のavi,私のAVL

220 篇文章 2 订阅
89 篇文章 0 订阅

题目

题目背景
我身旁的大佬:“你知道肿么随机一个 a v i \tt avi avi 出来吗?”

题目描述
大家都知道 A V L \rm AVL AVL 是满足这种性质的二叉搜索树:左右子树的高度之差 ⩽ 1 \leqslant 1 1 。高度定义为子树的根节点到子树内任意一个点的最远距离;空子树高度为 − 1 -1 1

现在给出一棵 A V L \rm AVL AVL 树,如果只保留其中的 k k k 个节点(如果 x x x 的子节点被保留,则 x x x 必须被保留)仍然是一个 A V L \rm AVL AVL 树,可以写出这 k k k 个节点的权值排序后形成的整数序列。请求出字典序最小的序列。

数据范围与提示
0 ⩽ k < n ⩽ 5 × 1 0 5 0\leqslant k<n\leqslant 5\times 10^5 0k<n5×105 。提示:树的高度是 O ( log ⁡ n ) \mathcal O(\log n) O(logn) 的。

思路

目前我们已经有了 O ( n log ⁡ 2 n ) \mathcal O(n\log^2n) O(nlog2n) 的做法(见上)、 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)做法,和一个 O ( n ) \mathcal O(n) O(n) 但是有常数的做法。

大体思路是类似于 I I I D X \rm IIIDX IIIDX 的,也就是贪心选择,然后 预订 一些点,来满足题目要求。在这道题里,我们选择了一个点,则需要一路向上:如果自己是左儿子,那么右儿子高度至少需要为 h − 1 h-1 h1,要提前打好标记,预订好这些节点。

点名表扬 我们永远滴神 R a i n y b u n n y \rm \color{black}{R}\color{red}{ainybunny} Rainybunny,指出这里不需要判断自己是右儿子时可能过深——当字典序最小时,肯定是优先选左子树内的点,所以右子树不会有比左子树更深的机会。

高度至少为 h h h 时,点的数量是多少?其实就是
f h = f h − 1 + f h − 2 + 1 f_h=f_{h-1}+f_{h-2}+1 fh=fh1+fh2+1

跟树的形态无关。这可以通过归纳法很轻易地说明。类似地,我们还可以说明 f h f_h fh 选择的点可以是 f h − 1 f_{h-1} fh1 的超集。

为了方便,我们先转化成 先序遍历 检查。正确性是显然的——没有当前点,谈何子树啊?所以先检查当前点也没问题。

如果就按照上面的 t a g tag tag 方法做,已经是 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 了,毕竟树高是 O ( log ⁡ n ) \mathcal O(\log n) O(logn) 的。但是打上去的 t a g tag tag 无非就是当前子树内的 h e i g h t height height 嘛!一旦加入某个 d e p t h depth depth 的点,就会让 h e i g h t height height 增大,进而引起 t a g tag tag 变化。而且先序遍历保证了我们 已经走过了祖先节点,可以提前维护信息。

考虑维护 v [ d e p ] v[dep] v[dep] 为,加入一个深度为 d e p dep dep 的点时,哪些 t a g tag tag 会变化。那么我们可以 O ( 1 ) \mathcal O(1) O(1) 的检查当前点是否可以加入;如果可以,那么 v [ d e p ] v[dep] v[dep] 肯定会变为 v [ d e p + 1 ] v[dep+1] v[dep+1],修改一下即可。而 t a g tag tag 可以直接用 d f s \rm dfs dfs 回溯的留下的树的高度 h e i g h t height height 1 1 1 得到。

在往下递归时,首先要下放 t a g tag tag 。由于 f h − 1 f_{h-1} fh1 选择的点是 f h f_h fh 选择的点的子集,优先在左子树内选择 f t a g − 1 f_{tag-1} ftag1 肯定更好,对应的就是右子树至少要有 t a g − 2 tag-2 tag2 的高度;除非左子树高度不够,只配得上 t a g − 2 tag-2 tag2

往左子树递归时,当前的右子树就成为了 “到根节点路径上自己是左儿子” 需要考虑的一个右儿子。加入 v v v 数组即可,具体是多少需要自己想一想。毕竟代码因人而异嘛。回溯时记得删掉。

但是有什么东西是可以先加在一起,然后整体推进一个状态吗?矩阵 呗。这个比较像斐波 t r i c k \rm trick trick

所以时间复杂度就是 O ( n ) \mathcal O(n) O(n) 了,所谓的常数就是矩阵乘法。

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; '0'>c||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}
inline void getMax(int &x,const int &y){
	if(x < y) x = y;
}

struct Matrix{
	const static int N = 3;
	int a[N][N];
	void clear(){ memset(a,0,N*N<<2); }
	Matrix operator * (const Matrix &b) const {
		Matrix c; c.clear();
		rep(i,0,N-1) rep(j,0,N-1) if(a[i][j])
		rep(k,0,N-1) c.a[i][k] += a[i][j]*b.a[j][k];
		return c; // no module
	}
	void operator += (const Matrix &b){
		rep(i,0,N-1) rep(j,0,N-1)
			a[i][j] += b.a[i][j];
	}
	void operator -= (const Matrix &b){
		rep(i,0,N-1) rep(j,0,N-1)
			a[i][j] -= b.a[i][j];
	}
};

const int MAXN = 500005;
int fa[MAXN], son[MAXN][2];
const int LOGN = 30;
Matrix fibo[LOGN], step;

int heit[MAXN];
void scan(int x){
	rep(d,0,1) if(son[x][d]){
		scan(son[x][d]);
		getMax(heit[x],heit[son[x][d]]);
	}
	++ heit[x]; // itself
}

int tag[MAXN], cur, k;
Matrix v[LOGN]; bool ans[MAXN];
void solve(int x,int dep=0){
	if(tag[x]){ // counted
		ans[x] = true; // always
		if(tag[x] != 1){
			bool d = (heit[son[x][0]] < tag[x]-1);
			tag[son[x][d]] = tag[x]-1;
			tag[son[x][d^1]] = tag[x]-2;
		}
	}
	else{ // check & modify
		Matrix nxt = v[dep]*step;
		int zxy = nxt.a[0][0]-v[dep].a[0][0];
		if(zxy+cur+1 > k) // too many nodes
			return void(heit[x] = 0);
		ans[x] = true; cur += zxy+1;
		v[dep+1] += nxt; // into next level
		v[dep].clear(); // removed
	}
	if(son[x][0]){ // recurse LSON
		if(son[x][1]){
			const int &p = tag[son[x][1]];
			v[dep+p+2] += fibo[p];
		}
		solve(son[x][0],dep+1);
		if(son[x][1]){
			int &p = tag[son[x][1]];
			getMax(p,heit[son[x][0]]-1);
			v[dep+p+2] -= fibo[p]; // remove
		}
	}
	if(son[x][1]) solve(son[x][1],dep+1);
	heit[x] = max(heit[son[x][0]],heit[son[x][1]])+1;
}

int main(){
	int n = readint(), rt = 0;
	k = readint(); // global boundary
	step.a[0][1] = step.a[2][2] = 1;
	rep(i,0,2) step.a[i][0] = 1;
	fibo[0].a[0][2] = 1;
	rep(i,1,LOGN-1) // pre-compute
		fibo[i] = fibo[i-1]*step;
	for(int i=1,fa; i<=n; ++i){
		fa = readint();
		if(!(~fa)) rt = i;
		else son[fa][fa < i] = i;
	}
	scan(rt), solve(rt);
	rep(i,1,n) putchar(ans[i]^48);
	putchar('\n');
	return 0;
}

后记

点名批评 我们永远滴神 R a i n y b u n n y \rm \color{black}{R}\color{red}{ainybunny} Rainybunny,非要先打 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 的代码。

点名表扬 我们永远滴神 R a i n y b u n n y \rm \color{black}{R}\color{red}{ainybunny} Rainybunny,帮助我理清了思路。以及下面这件事中,他发挥了重要作用。

点名批评 O n e I n D a r k \sf OneInDark OneInDark,本来准备先优化 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 的代码,被雨兔骂醒了 T A T TAT TAT

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值