U460909 [BCSP小高2024T4]先序遍历/小羊的晚餐 题解

Part.1 有关本题

本蒟蒻想起这道巧妙 又毒瘤 的题,到处搜寻提交窗口。好不容易找到窗口,有花了 3 h 3h 3h 的时间调题。

本蒟蒻为了悲剧不再发生,于是出了这道题,有写下了这篇题解以供后人。

以下的题解默认以阅读过原题。

Part.2 前置芝士

Part.2-1 DFS序列

DFS序列
DFS序列就是先序遍历的别称,一课子树DFS序列连续

Part.3 题意

言归正传,开始讲题。首先我们说思路,题面中说有一只小羊,它拥有超能力改变某棵二叉树一个节点的父亲。它问最先序遍历最小的字典序。

Part.4 题解

Part.4-1 n^3 暴力

我们枚举一个节点 u u u,新父亲 F F F,将原父亲 f f f 连向 u u u 的边去掉,将 F F F 的一个儿子变为 u u u 即可。更改完毕后对它跑DFS生成答案,取最小字典序。

Part.4-2 转移思路

使用如上所述的DFS序列解题。
转移思路
红是原来的DFS序列,黄操作可以转换为将 [ 8 , 10 ] [8,10] [8,10] 区间( 8 8 8 的子树)前移至 6 6 6 后,绿操作将 [ 9 , 9 ] [9,9] [9,9] 区间后置到 11 11 11 后,紫操作强调了可以放在最后。

但是可以发现,自己和自己的左孩子之间不可以插入区间。

Part.4-4 贪心

考虑前移,那么我们假设有一段区间移动到了 i i i 位置后,那么原来后面的是 i + 1 i+1 i+1,现在变成了某位置 j j j。我们贪心让 j j j 位置小,此时我们移动来了区间 [ j , j + s z j − 1 ] [j,j+sz_j-1] [j,j+szj1] s z sz sz 表示子树大小)。

后置区间 i + 1 i+1 i+1 位置及其子树,那么原来 i i i 位置之后是 i + 1 i+1 i+1,现在变成了 i + 1 + s z i + 1 i+1+sz_{i+1} i+1+szi+1。它的判断是 O ( 1 ) O(1) O(1) 的,可以先判断完。

如果一个为后置的区间 [ l , l + s z l − 1 ] [l,l+sz_l-1] [l,l+szl1] 找到位置,假设插到了 k k k 位置前,那么 k k k 是满足 k k k 位置的值大于 l l l 且它前面可插数的第一个位置。

预处理前面需要的最小值和能否可以插数即可。

Part.5 代码

#include <bits/stdc++.h>
using namespace std;
#define N 100010
int mn[N],id[N],ls[N],rs[N],sz[N],can[N],tot;
void dfs(int u){
	id[++tot]=u; sz[u]=1; 
	if (ls[u]) dfs(ls[u]),sz[u]+=sz[ls[u]];
	if (rs[u]) dfs(rs[u]),sz[u]+=sz[rs[u]];
}void out(int l,int r){
	for (int i=l; i<=r; i++) cout<<id[i]<<" ";
}int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int t; cin>>t;
	while (t--){
		int n; cin>>n; tot=0; mn[n+1]=1000000;
		for (int i=1; i<=n; i++) cin>>ls[i]>>rs[i];
		dfs(1); int v1,v2,v3,tp=0;
		for (int i=1; i<=n; i++) can[i]=!ls[id[i]];
		for (int i=n; i>=1; i--) mn[i]=min(mn[i+1],id[i]);
		for (int i=1; i<=n; i++){
			if (mn[i+1]<id[i+1]&&can[i]){
				int j=i+1; //cout<<"::"<<i<<" "<<id[i+1]<<" "<<mn[i+1]<<"\n";
				for (;j<=n; j++) if(id[j]==mn[i+1]) break; 
				v1=i+1,v2=j,v3=j+sz[id[j]]; tp=1; 
				//cout<<":::"<<v1<<" "<<v2<<" "<<v3<<"\n";
				break;
			}
		}for (int i=2; i<=n; i++){
			//cout<<":::::"<<sz[id[i]]<<"\n";
			if (i+sz[id[i]]<=n&&id[i+sz[id[i]]]<id[i]&&(i<v1||i==v1&&id[i+sz[id[i]]]<=id[v2]||tp==0)){
				//cout<<"::"<<i<<" "<<i+sz[id[i]]<<"\n";
				v1=i; v2=i+sz[id[i]]; tp=2; v3=n+1; break;
			}
		}if (tp==0) out(1,n);
		else{ 
			if (tp==2){
				for (int j=v2+1; j<=n; j++){
					if (can[j-1]&&id[j]>id[v1]){
						v3=j; break;
					}
				}
			}out(1,v1-1); out(v2,v3-1);
			out(v1,v2-1); out(v3,n);
			//cout<<"\n";
			//cout<<"::"<<v1<<" "<<v2<<" "<<v3<<" "<<tp; 
		}cout<<"\n";
	}
} 
  • 34
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值