题解: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\),我们有两种操作。
-
删以 \(u\) 为根的树。相当于,在 \(T\) 时刻删以 \(u\) 为根的树,
跟没说一样。 -
删以 \(u\) 为根的树的下方 \(k\) 层。相当于,在某时刻删以 \(u\) 为根的树,这棵树再长了一会,这时该树就少了几层。
上面两操作代价都为 \(1\)。
我们发现,优先从上到下操作 \(2\) 是最优的。实际上,\(1\) 操作也是 \(2\) 操作的特例。
反过来,另类理解:目标树变成完全树。
-
加以 \(u\) 为根的子树。
-
在以 \(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;
}
完结散花!!!

浙公网安备 33010602011771号