正题
很容易想到一个贪心,就是将直径提出来,对于不在直径上的点,都可以取到删除它的最大贡献,对于直径上的点随便删答案都是一样的.这个贪心在题解中也只证明了选任意两条不相同的直径答案一样,且对于不在直径链上的其他点有最大答案,但是并没有证明为什么这种"链"的划分一定是最优的.
苦思冥想很久才证明了正确性:
对于一种方案来说,如果删除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]);
}

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

被折叠的 条评论
为什么被折叠?



