题意:
给一棵树,执行n次操作,每次操作流程如下:
1. 选两个叶子节点
2. 将答案加上这两个节点的距离
3. 删去任意一个节点
要求最后答案最大。
问最大的答案以及构造出操作步骤。
做法:
一个简单的想法就是找一个深度最深的叶子,去更新别的叶子。
我们来看具体的情况。假设是下面这张图。
红色的点就是深度最深的点,绿色是深度次深的点,且他们是处在根的不同儿子所在的子树。
于是对于最右边那个儿子的子树,肯定是累加它和红点的距离,并把右边那个子树从叶子一个个删掉。
假如还有别的子树的话,也都是累加节点和红点的距离,并把右边那个子树从叶子一个个删掉。
最后根节点只剩下两个儿子,分别是存在最长的链和次长链的两个子树。
我们重新看一幅图。
这个时候我们肯定先会删那些白色的叶子节点,直到删完了以后,最后会变成一条链。
于是我们在根到红、绿点的路径上都打标记。(根打不打都可以)
这就表示dfs记录答案先跳过这链上的点。
对于剩下的点,不难发现只有两种情况:和红点组队或和绿点组队。
一个做法就是找到它和红点、绿点的lca计算出距离,比较距离较大的就和它组队,并删去。
于是最后就只剩下一条链了。
这个时候我们随便删就行了。因为无论如何答案都是加上len加到1。(len是链长)
于是算法大致结束。代码细节比较多QAQ,需要耐心和仔细思考。
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#define PII pair<int, int>
#define mp make_pair
using namespace std;
typedef long long LL;
const int N = 200010;
int n, rt, xx, yy, cnt, mx1, mx2, tot, MAX;
LL sum;
int head[N], len[N], depth[N], d[N], fa[N][20], m1[N], m2[N];
bool vis[N];
pair<int, PII> ans[N];
struct Edge{ int to, nex; }e[N<<1];
inline void add(int x, int y){ e[++ cnt].to = y; e[cnt].nex = head[x]; head[x] = cnt; }
inline void dfs(int u, int lst, int s)
{
int mx = 0; depth[u] = s; fa[u][0] = lst;
for(int i = head[u]; i; i = e[i].nex) {
int v = e[i].to; if(v == lst) continue;
dfs(v, u, s+1); mx = max(mx, len[v]);
}
len[u] = mx+1;//len[]表示以u为根子树从u出发最长链的长度
}
inline int find(int u, int lst)
{
vis[u] = 1; int mx = 0;
for(int i = head[u]; i; i = e[i].nex) {
int v = e[i].to; if(v == lst) continue;
if(!mx || len[v] > len[mx]) mx = v;//找最长链所在的那个儿子
}
if(!mx) return u; else return find(mx, u);//并沿着这条路走下去,标记经过的点
}
inline void getans(int u, int lst, int p)
{
for(int i = head[u]; i; i = e[i].nex) {
int v = e[i].to; if(v == lst) continue;
getans(v, u, p);
}
ans[++ tot] = mp(u, mp(p, u)); sum += depth[p] + depth[u];//加到答案的集合里去
}
inline void longest(int u, int lst, int s)
{
m1[u] = m2[u] = s;//保存最大和次大的depth
for(int i = head[u]; i; i = e[i].nex) {
int v = e[i].to; if(v == lst) continue;
longest(v, u, s+1);
if(m1[v] > m1[u]) m2[u] = m1[u], m1[u] = m1[v]; else if(m1[v] > m2[u]) m2[u] = m1[v];
}
if(m1[u]+m2[u]-2*s > MAX) MAX = m1[u]+m2[u]-2*s, rt = u;
}
inline int LCA(int x, int y)//倍增lca
{
if(depth[x] < depth[y]) swap(x, y);
int tmp = depth[x] - depth[y];
for(int i = 18; i >= 0; i --)
if(tmp>>i&1) x = fa[x][i];
if(x == y) return x;
for(int i = 18; i >= 0; i --)
if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
inline int getdis(int x, int y)
{
int z = LCA(x, y);
return depth[x] + depth[y] - 2*depth[z];
}
inline void get(int u, int lst)
{
for(int i = head[u]; i; i = e[i].nex) {
int v = e[i].to; if(v == lst) continue;
get(v, u);
}
if(!vis[u]) {//对于未标记的点才可加入答案
int dis1 = getdis(u, xx), dis2 = getdis(u, yy);//取距离较大的(由于我比较菜,只会暴力找lca计算距离,事实上是可以省略找lca的qwq
if(dis1 > dis2) {
ans[++ tot] = mp(u, mp(xx, u));
sum += dis1;
} else {
ans[++ tot] = mp(u, mp(yy, u));
sum += dis2;
}
}
}
int main()
{
scanf("%d", &n);
for(int i = 1; i < n; i ++) {
int x, y; scanf("%d%d", &x, &y);
add(x, y); add(y, x); d[x] ++; d[y] ++;
}
if(n == 2) { printf("1\n1 2 1\n"); return 0; }//只有两个点特判
for(int i = 1; i <= n; i ++) if(d[i] > 1) { rt = i; break; }
longest(rt, 0, 0);//找树的直径,在直径上找一个点作为根
mx1 = 0, mx2 = 0;//mx1和mx2存最长和次长的路径所在的父亲的儿子
for(int i = head[rt]; i; i = e[i].nex) {
int u = e[i].to; dfs(u, rt, 1);
if(!mx1 || len[u] > len[mx1]) mx2 = mx1, mx1 = u;
else if(!mx2 || len[u] > len[mx2]) mx2 = u;
}
for(int j = 1; j <= 18; j ++)
for(int i = 1; i <= n; i ++) fa[i][j] = fa[fa[i][j-1]][j-1];
xx = find(mx1, rt); yy = find(mx2, rt);//xx,yy是最长、次长路径的叶子节点
//在找xx,yy的过程中,标记根到xx,根到yy的路径上的点,表示最后剩下这条链
for(int i = head[rt]; i; i = e[i].nex) {
int v = e[i].to; if(v == mx1 || v == mx2) continue;
getans(v, rt, xx);//除去最长链、次长链所在的子树,另外子树的点都通过最长链的叶子节点消去
}
get(mx1, rt); get(mx2, rt);//对于最长链、次长链的子树中非标记的点,分别考虑和xx、yy的距离,取距离较大的那个
//最后剩下的一条链,发现无论怎么取都一样,所以就随便构造一波
//xx,yy分别跳到根即可
while(xx != rt) {
ans[++ tot] = mp(xx, mp(yy, xx));
sum += depth[xx] + depth[yy];
xx = fa[xx][0];
}
while(yy != rt) {
ans[++ tot] = mp(yy, mp(xx, yy));
sum += depth[xx] + depth[yy];
yy = fa[yy][0];
}
cout << sum << endl;
for(int i = 1; i <= tot; i ++) printf("%d %d %d\n", ans[i].first, ans[i].second.first, ans[i].second.second);
return 0;
}