codeforces911F - Tree Destruction

题面在这里

题意:

给一棵树,执行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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值