题解:CF1225F Tree Factory
题目传送门
题意
给出一棵树,要求构造一条链,和一个操作序列,使得经过这些操作后,这条链可以变成给定的树。只有一种操作,把自己变成自己父亲的兄弟,即断开自己和父亲的连边,和父亲的父亲连边。
思路
考虑到正着做不好做,所谓正难则反,我考虑倒着做。那么题目就变成了,要求把给定的树变成一条链,每次的操作是把一条链与另一条链合并,贪心的发现,合并的代价是短的那条链的长度。基于这个性质,我们可以推出一个正确的贪心策略:对树进行长链剖分,每次都从子树下的最长链开始向其他链合并,代价就是除最长链之外其他链的长度总和。
如果手玩几次之后就会发现,最终的链上的节点的顺序对应着长链剖分后的 dfn 序。这样我们就可以轻松的解决第一个问题。现在考虑怎么输出操作序列,我们观察 dfn 序会发现,如果当前节点 dfn 序的父亲的深度和前一个节点的 dfn 的深度不相同,就说明这两个节点之间发生了操作,基于操作的性质,我们只需要计算这两个深度之间相差多少,即可知道操作了多少次,因为我们的贪心是从长链往短链合并,所以越靠后的点说明在原树中所处的链越长,那么每次操作我们都拿当前节点操作即可。
文字叙述不好理解,可以借助代码多看几次。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1010101;
ll n,dep[N],head[N],tot,siz[N],f[N],dfn[N],son[N],ans,cnt;
struct edge{ll to,next;}e[N];
void add(ll u,ll v){
e[++tot].to=v;
e[tot].next=head[u];
head[u]=tot;
}
void dfs(ll u,ll fa){
f[u]=fa,dep[u]=dep[fa]+1;
for(int i=head[u];i;i=e[i].next){
ll v=e[i].to;
if(v==fa)continue;
dfs(v,u);
siz[u]=max(siz[u],siz[v]+1);
if(siz[v]+1>siz[son[u]])son[u]=v;
}
}
void dfs1(ll u,ll fa){
dfn[++cnt]=u;
for(int i=head[u];i;i=e[i].next){
ll v=e[i].to;
if(v==fa||v==son[u])continue;
dfs1(v,u);
}
if(son[u])dfs1(son[u],u);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=2;i<=n;i++){
ll x;
cin>>x;x++;
add(i,x);
add(x,i);
}
dfs(1,0);
dfs1(1,0);
for(int i=1;i<=n;i++)cout<<dfn[i]-1<<" ";
for(int i=2;i<=n;i++)ans+=dep[dfn[i-1]]-dep[f[dfn[i]]];
cout<<"\n"<<ans<<"\n";
for(int i=2;i<=n;i++){
for(int j=dep[f[dfn[i]]];j<dep[dfn[i-1]];j++){
cout<<dfn[i]-1<<" ";
}
}
return 0;
}

浙公网安备 33010602011771号