题解|算法竞赛进阶指南 巡逻

A-巡逻_0x63 图论-树的直径与最近公共祖先

https://ac.nowcoder.com/acm/contest/1057/A

k=1时,如果我们新修建一个道路,那么就会有一段路程少走一遍。此时选择连接树的直径的两个端点显然是最优的。

难就难在k=2的时候,还是上面的思路,先连接两个直径的端点。假设我们接下来连接的是x,y两个叶子结点,它们到直径的距离分别为dis[x],dis[y],并设直径上两点的距离为d[u,v],这里u,v分别为x,y所在链和直径的交点。

因此最后的答案会增加d[u,v]-dis[x]-dis[y]。要使答案最小,那么也就是使dis[x]+dis[y]-d[u,v]最大。这其实就是把u到v路径上的边权取反后,树的最长链。

再求一次树的直径就好了。注意:因为最后有负边权存在,用dfs/bfs来求会出错。所以得用树形dp。

这道题考察了求直径的两种方法,不失为一道好题。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 100010;
const ll INF = 2147483647;
ll n,k,x,y,st,ed,tem,fa[maxn],res,cnt,d[maxn],tot;
ll cur,ans;
bool vis[maxn],h[maxn];
vector<ll> e[maxn];
void dfs(ll x,ll step,ll tag){
    vis[x] = 1;
    bool pd = true;
    for(int i = 0;i < e[x].size();i++){
        if(!vis[e[x][i]]){
            pd = false;
            if(!tag)fa[e[x][i]] = x;
            dfs(e[x][i],step + 1,tag);
        }
    }
    if(pd && tem < step){
        tem = step;
        if(tag)st = x;
        else ed = x;
    }
    return;
}
void mark(ll x,ll step){
    vis[x] = 1;
    ll pd = true;
    for(int i = 0;i < e[x].size();i++){
        if(!vis[e[x][i]] && !h[e[x][i]]){
            pd = false;
            mark(e[x][i],step + 1);
        }
    }
    if(pd)cur = max(cur,step);
    return;
}
void dp(ll x){
    vis[x] = 1;
    for(int i = 0;i < e[x].size();i++){
        if(!vis[e[x][i]]){
            dp(e[x][i]);
            ans = max(ans,1ll*(d[x] + d[e[x][i]] + (h[x] == 1 && h[e[x][i]] == 1 ? -1 : 1)));
            d[x] = max(d[x],1ll*(d[e[x][i]] + (h[x] == 1 && h[e[x][i]] == 1 ? -1 : 1)));
        }
    }
}
int main()
{ 
    ios::sync_with_stdio(false);
    cin>>n>>k;
    for(int i = 1;i < n;i++){
        cin>>x>>y;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    dfs(1,0,1);
    memset(vis,0,sizeof(vis));
    tem = 0;
    dfs(st,0,0);
    if(k == 1){
        res = (n - 1) * 2 + 1;
        tem = ed;
        while(tem != st){
            res--;
            tem = fa[tem];
        }
        cout<<res;
    }
    else{
        res = (n - 1) * 2 + 2;
        tem = ed;
        while(tem != st){
            h[tem] = 1;
            res--;
            tem = fa[tem];
        }
        h[st] = 1;
        memset(vis,0,sizeof(vis));
        dp(1);
        res -= ans;
        cout<<res;
    }
    return 0;
}
全部评论

相关推荐

12-06 01:10
已编辑
哈尔滨工程大学 Java
一面问的真细,二面不知为啥变双机位。9.29快手主站平时怎么学习&nbsp;AI&nbsp;的,国内外知名大模型,实习公司都用的什么大模型,怎么评估效果的java池化思想,线程池构造方法的核心参数,线程池中阻塞队列注意事项,submit方法参数和执行逻辑,shutdown和shutdownnow,核心线程允许过期吗threadlocal底层,为什么key是弱引用,key回收了再get或者set这个value会怎样aqs,如何保证公平性java代理java堆划分,新生代还有别的晋升老年代的情况吗,什么时候触发gc,gc失败抛什么异常,如何排查oom,导出dump命令redis数据结构,哪个底层是跳表,和其他数据结构对比布隆过滤器会出现大key问题吗,你咋实现的布隆过滤器你怎么实现redis分布式锁,可重入,续期聚簇索引非聚簇索引select语句会加锁吗,怎么实现的不加锁undolog&nbsp;redolog&nbsp;binlog怎么能让select加锁,update这个范围加的什么锁,update一条呢手撕简单01背包,接雨水10.10快手主站意图识别用的哪个大模型,走到意图和rag的比例,faq是点击的吗自然语言怎么识别的gap一年干啥了,转正怎么样没跟组里提意向吗,研究生研究方向是传统算法吗,会大模型微调吗注册场景为什么用布隆过滤器,原理分布式锁底层的key怎么拼的,value里是什么redis持久化zset底层mysql索引结构,一个表三个字段有主键唯一索引和没索引的字段会有几个b+树,聚簇索引非聚簇索引存的啥无手撕
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务