CodeForces - 592D(找树的直径)

本文探讨了一种特定的图论问题:在一个给定的树形结构中寻找覆盖特定节点集的最短路径,并确定最优起始节点。通过两步深度优先搜索策略找到了最优解,第一步确定覆盖所有必经节点的子树,第二步找出这棵子树的直径,从而计算最短路径长度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给出一颗节点数为n的树,其中有m个点必须要访问到,起点可以任意,每一次只能从当前点走到相邻点,每个点可以重复走,每走一步需要花费一个单位的时间,求把m个点走完最少需要花费的时间,并使得起点的编号最小.

这里写图片描述

1.根据观察可以发现,起点一定是m个点中的一个,很明显如果起点不是红色的点,你需要先走到一个红色的点上去. 
2.如果想要走完所有的m个点,则一定会走完一颗子树,这颗子树包含这所有的m个点,换句话说这m个点可以唯一确定一颗子树 
3.如果题目要求最后还要返回起点的话,可以观察出需要花费的时间 = 这颗子树的边数 * 2. 
4.不需要返回,则可以在这颗子树上找到一条最长的边,也就是这棵树的直径,然后沿着这个直径访问完所有的m个点,这样需要的时间为,子树的边数*2 - 直径的边数. 
5.这样问题就转化问,先求出这颗子树,然后找到一条端点尽量小的,最长的直径. 
6.求树的直径有个经典思想,可以从任意一个点出发,然后dfs(bfs)找到距离该点最远的点t,再从t点出发找到一个距离t点最远的点s,则s-t就是树的直径.具体证明网上有很多,这里就不再累赘. 
7.所以这里可以从任意一个红色的点出发,找到这颗子树,并找到一个距离最远且编号尽量小的端点,然后在这颗子树上在进行一次dfs就可以解决这个问题.这里只需要注意更新距离的时候同时还要考虑端点的编号大小即可.

针对这道题,最小的出发点只有可能出现在dfs1之后,最远且相对较小的点上,或者dfs2之后最远且相对较小的点上,即直径两端点中较小的那个

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=123456+5;

vector<int> G[maxn];
int dis[maxn];
bool A[maxn],on[maxn];
bool visit[maxn];
int tar,d,cnt;
int n,m,u,v;
bool dfs1(int u)//求出子树
{
    visit[u]=true;
    bool flag=false;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(!visit[v])
        {
            dis[v]=dis[u]+1;
            flag|=dfs1(v);//或运算符 只要一个为1就是1
        }
    }
    if(A[u])
    {
        flag=true;
        if(dis[u] > d || (dis[u]==d&& u<tar) )//找出发点
        {
            d=dis[u];
            tar=u;
        }
    }
    if(flag)
    {
        on[u]=true;
        cnt++;
    }
    return flag;
}
void dfs2(int u)//找最长路
{
    visit[u]=true;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(on[v] && !visit[v])
        {
            dis[v]=dis[u]+1;
            if(dis[v]>d || ( dis[v]==d && v<tar))//找最远的这条边上最小的点
            {
                d=dis[v];
                tar=v;
            }
            dfs2(v);
        }
    }
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    cin>>n>>m;
    for(int i=0;i<n-1;i++)
    {
        cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    for(int i=0;i<m;i++)
    {
        cin>>u;
        A[u]=true;//该点为指定的点
        tar=u;
    }
    dfs1(tar);
    int minn=tar;
    memset(visit,0,sizeof(visit));
    fill(dis,dis+n+1,0);
    d=0;
    dfs2(tar);
    minn=min(minn,tar);//最小出发点只有可能在直径的两端
    cout<<minn<<endl;
    cout<<2*(cnt-1)-d<<endl;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值