题解:P16428 「YLLOI-R4-T4」稻香

前置芝士

  • 前、中、后序遍历。

关于存树

建议使用结构体。

struct tree{
  int l,r;
}T[NUM];

也可以用数组。

int l[NUM],r[NUM];

关于前序遍历

因为前序遍历先输出根节点,所以比较大小直接换就行了。

void q(int p){
	if(T[p].l>T[p].r&&k){
		swap(T[p].l,T[p].r);
		k--;
	}
	cout<<p<<' ';
	if(T[p].l) q(T[p].l);
	if(T[p].r) q(T[p].r);
}

关于中、后序遍历

因为他俩都一样都是先输出叶节点。首先引入两个变量 \(m,fa\)

struct tree{
	int l,r;
	int m;//子树最小值
  int fa;//父节点
}T[NUM];

如果右子树比左子树的最小值小,所以交换会更优。

int getmin(int p){
	if(T[p].m) return T[p].m;
	if(T[p].l==0&&T[p].r==0)
		return T[p].m=p;
	return T[p].m=min(getmin(T[p].l),getmin(T[p].r));
}

\(fa\) 是干神魔的?首先来看一组样例。

B
7 1 1
4 5
0 0
0 0
6 3
7 2
0 0
0 0
      1 
    /   \
  4       5
 / \     / \
6   3   7   2

可以发现答案应是:3 4 6 1 7 5 2
但如果只取最小值进行比较,错误答案是:7 5 2 1 6 4 3
为什么呢?因为只找最小值无法判断交换之后最小值能否做出贡献。
我们设以 \(rot\) 为根节点使最小值(叶节点 \(p\))做出贡献的交换次数为 \(pos\)
求出 \(pos\) 的值,只需要向上递归,记录从 \(fa\) 右节点通过次数即为 \(pos\)

int getpos(int p,int rot){
	int ans=0;
	while(p!=rot){
		if(T[T[p].fa].r==p) ans++;
		p=T[p].fa;
	}
	return ans;
}

同时可以在求最小值的时候顺便求出来子树中的叶节点可以到该子树最左侧的点的最小值。

int getmin(int p){
	if(T[p].l==0&&T[p].r==0)
		return T[p].m=p;
	T[p].m=INT_MAX;
	if(getpos(getmin(T[p].l),T[p].fa)<=k) T[p].m=min(T[p].m,T[T[p].l].m);
	if(getpos(getmin(T[p].r),T[p].fa)<=k) T[p].m=min(T[p].m,T[T[p].r].m);
	return T[p].m;
}

这样中、后序遍历代码就可以写出来了。

void z(int p){
	if(k && getmin(T[p].l)>getmin(T[p].r)){
		swap(T[p].l,T[p].r);
		k--;
	}
	if(T[p].l) z(T[p].l);
	cout<<p<<' ';
	if(T[p].r) z(T[p].r);
}

void h(int p){
	if(k && getmin(T[p].l)>getmin(T[p].r)){
		swap(T[p].l,T[p].r);
		k--;
	}
	if(T[p].l) h(T[p].l);
	if(T[p].r) h(T[p].r);
	cout<<p<<' ';
}

代码

对前面的代码超级拼装即可。

总代码

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int NUM=2e5+10;
struct tree{
	int l,r;
	int m,fa;
}T[NUM];
char op;
int n,root,k;

int getpos(int p,int rot){
	int ans=0;
	while(p!=rot){
		if(T[T[p].fa].r==p)ans++;
		p=T[p].fa;
	}
	return ans;
}
int getmin(int p){
	if(T[p].l==0&&T[p].r==0)
		return T[p].m=p;
	T[p].m=INT_MAX;
	if(getpos(getmin(T[p].l),T[p].fa)<=k) T[p].m=min(T[p].m,T[T[p].l].m);
	if(getpos(getmin(T[p].r),T[p].fa)<=k) T[p].m=min(T[p].m,T[T[p].r].m);
	return T[p].m;
}

void q(int p){
	if(T[p].l>T[p].r&&k){
		swap(T[p].l,T[p].r);
		k--;
	}
	cout<<p<<' ';
	if(T[p].l) q(T[p].l);
	if(T[p].r) q(T[p].r);
}

void z(int p){
	if(k&&getmin(T[p].l)>getmin(T[p].r)){
		swap(T[p].l,T[p].r);
		k--;
	}
	if(T[p].l) z(T[p].l);
	cout<<p<<' ';
	if(T[p].r) z(T[p].r);
}

void h(int p){
	if(k&&getmin(T[p].l)>getmin(T[p].r)){
		swap(T[p].l,T[p].r);
		k--;
	}
	if(T[p].l) h(T[p].l);
	if(T[p].r) h(T[p].r);
	cout<<p<<' ';
}

int main(){
	cin>>op>>n>>root>>k;
	for(int i=1;i<=n;++i){
		int l,r;
		cin>>l>>r;
		T[i].l=l;
		T[l].fa=i;
		T[i].r=r;
		T[r].fa=i;
	}
	if(op=='A') q(root);
	if(op=='B') z(root);
	if(op=='C') h(root);
	return 0;
}

交上去发现他五颜六色。为什么呢。

看一组样例

C
15 1 1
4 5
10 11
14 15
6 3
7 2
12 13
9 8
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0

               1
           /       \
       4               5
     /   \           /   \
   6       3        7      2
 /   \   /   \    /  \   /   \
12   13 14   15  9    8 10   11
9 8 7 10 11 2 5 12 13 6 14 15 3 4 1

感谢 @zhangyizhan 提供的 hack。
到最后我也没弄明白它为什么没换过去。
因为它还爆 RE,@Geogruffy 另辟蹊径,将 getmingetpos 两个函数合二为一于是就有了下面这个代码。

int getmin(int p,int k){
    //没有子节点
    if (!T[p].l) return p;

    //没有步数,只能找左边
    if (k == 0) return getmin(T[p].l,0);

    //往左找不消耗步数
    int s1 = getmin(T[p].l,k);
    //往右找消耗步数
    int s2 = getmin(T[p].r,k-1);
    return min(s1,s2);
}

很神奇啊,前面的问题都解决了。
吸氧 207ms 过。

AC 代码

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int NUM=2e5+10;
struct tree{
	int l,r;
	int m,fa;
}T[NUM<<1];
char op;
int n,root,k;

int getmin(int p,int k){
    if (!T[p].l) return p;
    if (k == 0) return getmin(T[p].l,0);
    int s1 = getmin(T[p].l,k);
    int s2 = getmin(T[p].r,k-1);
    return min(s1,s2);
}

void q(int p){
	if(T[p].l>T[p].r&&k){
		swap(T[p].l,T[p].r);
		k--;
	}
	cout<<p<<' ';
	if(T[p].l) q(T[p].l);
	if(T[p].r) q(T[p].r);
}

void z(int p){
	if(k&&getmin(T[p].l,k)>getmin(T[p].r,k-1)){
		swap(T[p].l,T[p].r);
		k--;
	}
	if(T[p].l) z(T[p].l);
	cout<<p<<' ';
	if(T[p].r) z(T[p].r);
}

void h(int p){
	if(k&&getmin(T[p].l,k)>getmin(T[p].r,k-1)){
		swap(T[p].l,T[p].r);
		k--;
	}
	if(T[p].l) h(T[p].l);
	if(T[p].r) h(T[p].r);
	cout<<p<<' ';
}

int main(){
	cin>>op>>n>>root>>k;
	for(int i=1;i<=n;++i){
		int l,r;
		cin>>l>>r;
		T[i].l=l;
		T[l].fa=i;
		T[i].r=r;
		T[r].fa=i;
	}
	if(op=='A') q(root);
	if(op=='B') z(root);
	if(op=='C') h(root);
	
	return 0;
}

优化

仔细看就会发现很多东西在这个新的 getmin 面前显得冗杂,可以直接删掉,甚至连结构体都不需要了。

@Geogruffy@NaHCO3_Ciallo@X_ey1267 和我自己的优化后来到 60ms(极限 57ms,稳定 63ms),成功拿到最优解。

最优解

#include<bits/stdc++.h>

#ifdef __unix__
#define getchar getchar_unlocked
#else
#define getchar _getchar_nolock
#endif
using namespace std;
const int NUM=131082;

int l[NUM],r[NUM];
char op;
int n,root,k;

inline int read(){
	int x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		ch=getchar();
	}
	while (ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=getchar();
	}
	return x;
}
inline void write(int x) {
   if (x>9) write(x/10);
   putchar(x%10+'0');
}

int getmin(int p,int k){
    if (!l[p]) return p;
    if (k == 0) return getmin(l[p],0);
    int s1 = getmin(l[p],k);
    int s2 = getmin(r[p],k-1);
    return min(s1,s2);
}

void q(int p){
	if(l[p]>r[p]&&k){
		swap(l[p],r[p]);
		k--;
	}
	write(p),putchar(' ');
	if(l[p]) q(l[p]);
	if(r[p]) q(r[p]);
}

void z(int p){
	if(k&&getmin(l[p],k)>getmin(r[p],k-1)){
		swap(l[p],r[p]);
		k--;
	}
	if(l[p]) z(l[p]);
	write(p),putchar(' ');
	if(r[p]) z(r[p]);
}

void h(int p){
	if(k&&getmin(l[p],k)>getmin(r[p],k-1)){
		swap(l[p],r[p]);
		k--;
	}
	if(l[p]) h(l[p]);
	if(r[p]) h(r[p]);
	write(p),putchar(' ');
}

int main(){
	op=getchar();
	n=read();
	root=read();
	k=read();
	for(int i=1;i<=n;++i){
		l[i]=read();
		r[i]=read();
	}
	if(op=='A') q(root);
	else if(op=='B') z(root);
	else h(root);
	return 0;
}

完结撒花。


特别鸣谢:

@Geogruffy
@NaHCO3_Ciallo
@X_ey1267
@zhangyizhan

posted @ 2026-06-27 18:43  LZYXT  阅读(5)  评论(1)    收藏  举报