CF911F Tree Destruction

Link\text{Link}

前置知识

题意

一棵 nn 个点的无根无权树,进行 n1n-1 次操作,每次进行如下操作:

  • 选择树上的两个点 u,vu,v
  • ansans 加上 u,vu,v 之间的简单路径经过的边数。
  • 删除点 uu 或点 vv,并输出删除过程。

ansans 的最大值。

分析

我们知道一个点在树上能最远达到的点必定是树的直径的端点,为了使答案最大,我们要使每个点的贡献最大,于是就可以先把不在直径上的点删去(要保证不影响其他点的最大贡献),再逐个删直径上的点。

我们就有了一个贪心策略:

  • 求出树的直径长度 lenlen、两个端点 s,ts,t
  • 用两次 dfs\text{dfs} 计算每个点到 s,ts,t 的距离 dsi,dtids_i,dt_i
  • 答案 ansans 初始为 i=1leni=len×(len+1)2\sum_{i=1}^{len}i=\frac{len\times(len+1)}{2},也就是最后要逐个删掉直径上的点,贡献为 i=1leni\sum_{i=1}^{len}i
  • 通过比较 dsi+dtids_i+dt_i 是否和 lenlen 相等判断点 ii 是否是直径上的点,并打上标记 visi1vis_i\Leftarrow 1
  • 对于所有不在直径上的点 iiansans 加上 iisstt 的最大距离 max(dsi,dti)\max(ds_i,dt_i)。这个过程可以和上一步一起实现。
  • 输出 ansans
  • 不断找出一个不在直径上的点直至没有,要保证这个点所连接的点到直径任意一端的路径不经过这个点,删除这个点并输出过程。可以这样理解:把直径 sts\Rightarrow t 这条线举起来,让其他点沿地心引力自由下落,形成一颗颗子树,一个节点可以删除当且仅当它的子节点都被删除,作者画图技术太烂请读者自行脑补
  • 选择直径的一端,从这一端开始删直径上的节点并输出过程。

求出 ansans 显然十分简单,但是求出删点过程却比较麻烦,这也正是这篇题解与其他题解的不同之处:如何求。

我们可以在输入加边时记录每个点所连的边数 iniin_i,对于每个不在直径上的点 ii,令 iniin_i 自减,也就是减去它到父节点的一条边(树的形态就是贪心策略中所说的,只不过我们暂时不知道具体长什么样),此时一个没有子节点的 iiiniin_i00,仿照拓扑排序的做法,将 ini=0in_i=0 的节点入队。

不断取出队头 xx,输出 xx 点的删除过程,把所有与 xx 相连的点 yyinyin_y11,使父节点 yyinyin_y 减少 11,如果 iny=0in_y=0,把 yy 入队。

上述操作执行到队列为空,我们就删完了所有不在直径上的点。

Q: 为什么要把所有与 xx 相连的点 yyinyin_y11?这样不是会把子节点 yyinyin_y 重复减了吗?

A: 这样是使其父节点的边数减少,让其父亲成为一个新的叶子节点;子节点重复减不会有影响,因为这些节点的 inin 之前已经为 00,再减 11 就变成负数,不会被入队。

处理好不在直径上的点后,我们从直径的一端开始 dfs\text{dfs} 删点并输出过程即可。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
	long long x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
void write(long long x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
const int N=2e5+10;
int n,u,v,in[N];
vector<int>e[N];
bool vis[N];
#define T_D_T long long
struct Tree_Diameter{
	int s,t;
	T_D_T d[N],len;
	void init(){
		s=t=len=0;
		memset(d,0,sizeof(d));
	}
	void dfs(int x,int fa){
		for(int i=0;i<e[x].size();i++){
			if(e[x][i]==fa)
				continue;
			d[e[x][i]]=d[x]+1;
			if(d[e[x][i]]>d[s])
				s=e[x][i];
			dfs(e[x][i],x);
		}
	}
	void calc(){
		init();
		dfs(1,0);
		d[t=s]=0;
		dfs(s,0);
		len=d[s];
	}
}tree;
ll D[2][N];
void dfs(int t,int x,int fa){
	for(int i=0;i<e[x].size();i++){
		if(e[x][i]==fa)
			continue;
		D[t][e[x][i]]=D[t][x]+1;
		dfs(t,e[x][i],x);
	}
}
void topu(){
	queue<int>q;
	for(int i=1;i<=n;i++)
		if(!in[i])
			q.push(i);
	while(q.size()){
		int x=q.front();q.pop();
		printf("%d %d %d\n",(D[0][x]>D[1][x]?tree.s:tree.t),x,x);
		for(int i=0;i<e[x].size();i++)
			if(vis[e[x][i]]&&!(--in[e[x][i]]))
				q.push(e[x][i]);
	}
}
void dfs2(int x,int fa){
	if(x==tree.t)
		return;
	printf("%d %d %d\n",tree.t,x,x);
	for(int i=0;i<e[x].size();i++)
		if(!vis[e[x][i]]&&e[x][i]!=fa)
			dfs2(e[x][i],x);
}
int main(){
	n=read();
	for(int i=1;i<n;i++){
		u=read();v=read();
		e[u].push_back(v);
		e[v].push_back(u);
		in[u]++;in[v]++;
	}
	tree.calc();
	ll ans=(tree.len+1)*tree.len/2;
	dfs(0,tree.s,0);dfs(1,tree.t,0);
	for(int i=1;i<=n;i++)
		if(D[0][i]+D[1][i]!=tree.len){
			ans+=max(D[0][i],D[1][i]);
			vis[i]=1;
			in[i]--;
		}
	write(ans);cout<<endl;
	topu();
	dfs2(tree.s,0);
	return 0;
}

时间复杂度 O(n)O(n)

posted @ 2021-09-21 23:58  luckydrawbox  阅读(17)  评论(0)    收藏  举报  来源