题解:P13279 「CZOI-R4」生长的树

P13279 「CZOI-R4」生长的树题解

备注

看了两篇题解,发现都有所有减去重复的思想,但逆推不是很好理解,这里发一篇顺推的。

本文尽量使用短句,避免被喷

翻译

本题虽然用中文写,但理解起来非常难崩,极易偏差。

所求的最终树只要形状一样即可,编号等无需考虑。删点后,树会继续生长。删点任意时刻都能进行,并非只在长好后删。

形象化的,如图,\(k=3\) 时,时刻 1 上半长到了图 1.1,在图 1.2 砍了一刀。上面两个是同一时刻。时刻 2 长到 图 2.1。

                   /O
 /O      /O      /O-O
/       /       /  \O
O-O  -> O    -> O-O
\       \       \  /O
 \O      \O      \O-O
                   \O
 1.1     1.2     2.1

分析

第一问时刻数:令根节点深度为 \(0\),一定等于最深的点的深度,因为你必须要生长到那个点。我们约定它为 \(T\)

第二问操作数:根据上文题面翻译,我们先让这棵树长到 \(T\) 时刻,不操作。对于这棵完全树,任意节点 \(u\),我们有两种操作。

  1. 删以 \(u\) 为根的树。相当于,在 \(T\) 时刻删以 \(u\) 为根的树,跟没说一样

  2. 删以 \(u\) 为根的树的下方 \(k\) 层。相当于,在某时刻删以 \(u\) 为根的树,这棵树再长了一会,这时该树就少了几层。

上面两操作代价都为 \(1\)

我们发现,优先从上到下操作 \(2\) 是最优的。实际上,\(1\) 操作也是 \(2\) 操作的特例。

反过来,另类理解:目标树变成完全树。

  1. 加以 \(u\) 为根的子树。

  2. 在以 \(u\) 为根的树的下方增加 \(k\) 层。

上面两操作代价都为 \(1\)

理解方式等价,本人用下面的,代码更好写。

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;

int n,k;
int dep[N],ans;
vector<int> g[N];

int ask_dep(int rt,int d,int fa){		//处理子树叶子的最大深度 
	dep[rt]=d;
	for(int i=0;i<g[rt].size();i++){
		if(g[rt][i]==fa)	continue;
		dep[rt]=max(dep[rt],ask_dep(g[rt][i],d+1,rt));
	}
	return dep[rt];
}

void dfs(int rt,int add,int fa){		//add 是操作 2 加的层数 
	int res=0;
	if(dep[rt]+add<dep[1])	ans++,add+=dep[1]-dep[rt]-add;	//还原操作 2 
	for(int i=0;i<g[rt].size();i++){
		if(g[rt][i]==fa)	continue;
		res++;
		dfs(g[rt][i],add,rt);
	}
	if(res==0)	return;		//是叶子 
	ans+=k-res;				//还原操作 1 
	return;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>k;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	
	ask_dep(1,0,-1);
	
	dfs(1,0,-1);
	
	cout<<dep[1]<<' '<<ans;
	return 0;
}

完结散花!!!

posted @ 2026-01-30 09:48  concert_b  阅读(0)  评论(0)    收藏  举报