Tree Destruction,CF911F,直径+细节证明

本文探讨了一种在树形结构中利用贪心策略寻找最优解的方法,通过对不同情况的分析和证明,展示了如何选取树的直径作为基础进行优化,确保了算法的正确性和效率。

正题

      很容易想到一个贪心,就是将直径提出来,对于不在直径上的点,都可以取到删除它的最大贡献,对于直径上的点随便删答案都是一样的.这个贪心在题解中也只证明了选任意两条不相同的直径答案一样,且对于不在直径链上的其他点有最大答案,但是并没有证明为什么这种"链"的划分一定是最优的.

      苦思冥想很久才证明了正确性:

      对于一种方案来说,如果删除x所选择的点y不是当前树中的最远点,那么在这一步就肯定不是最优方案,所以我们只需要考虑一个长度为n-1,值域在[1,n]的排列a,表示第i次选a[i]的贡献,这个排列要满足的就是a[i]必须是当前树的叶子.

      我们只考虑直径唯一的情况,对于直径不唯一的情况,可以强行找字典序最小的一条.

      删到某个时候,树必定呈现一条链的形状,记端点为x,y,此时我们从当前的链向前倒着考虑,变成了不断的加点,如果加点的选择点(这里指的是正着删除的时候选择的,让贡献最大的点)不是x或者y,设其为z,那么(x,z)(y,z)作为最后链的方案一定不比(x,y)作为最后链的方案,因为考虑最后删链的时候,代价是(1+...+链长),链长此时不会变短,所以链的代价不会劣.对于其他不在z所对应子树上(这里实际是考虑删掉被替换点z的在链上的交叉点时,z所在的联通块)的点,如果原来选择了被替换的点作为选择点,将选择点替换成z一定不会更劣.

      那么通过这样不断地换链的端点,到最后肯定会变成一条直径,答案也被优化到了最大.

      如果有锅,一定要联系我!

总结

      有时候贪心就是这么显然,但是找出证明方法才有利于更好的推广,理解,不能嫌繁琐,觉得显然,就跳过了,这种"显然"在赛场上可能会浪费更多的时间.

#include<bits/stdc++.h>
using namespace std;

const int N=200010;
struct edge{
	int y,nex;
}s[N<<1];
int first[N],len=0,n,mx,p;
int a[N],tot,t;
pair<int,int> ans[N];
long long op=0;

void ins(int x,int y){s[++len]=(edge){y,first[x]};first[x]=len;}

void dfs(int x,int fa,int dep){
	if(dep>mx) mx=dep,p=x;
	for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa) dfs(s[i].y,x,dep+1);
}

void ga(int x,int tar,int fa){
	if(x==tar){a[++tot]=tar;return ;}
	for(int i=first[x];i;i=s[i].nex) if(s[i].y!=fa){
		ga(s[i].y,tar,x);
		if(tot) {
			a[++tot]=x;
			return ;
		}
	}
}

void gas(int x,int fa,int pos,int dep){
	for(int i=first[x];i;i=s[i].nex) if(s[i].y!=fa) gas(s[i].y,x,pos,dep+1);
	op+=dep;ans[++t]=make_pair(pos,x);
}

int main(){
	scanf("%d",&n);
	int x,y;
	for(int i=1;i<n;i++) scanf("%d %d",&x,&y),ins(x,y),ins(y,x);
	mx=0;dfs(1,0,0);
	mx=0;x=p;dfs(p,0,0);
	ga(x,p,0);
	for(int i=1;i<=tot;i++){
		for(int j=first[a[i]];j;j=s[j].nex) if(s[j].y!=a[i-1] && s[j].y!=a[i+1])
			gas(s[j].y,a[i],i<=tot/2?a[tot]:a[1],max(i,tot-i+1));
	}
	printf("%lld\n",op+1ll*tot*(tot-1)/2);
	for(int i=1;i<=t;i++) printf("%d %d %d\n",ans[i].first,ans[i].second,ans[i].second);
	for(int i=1;i<=tot-1;i++) printf("%d %d %d\n",a[tot],a[i],a[i]);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值