洛谷 P1395 会议 树的重心 dfs 最短路径和 bfs dijkstra

题目连接:

https://www.luogu.com.cn/problem/P1395

参考博客:

https://www.luogu.com.cn/blog/Five-shifts-Forever/solution-p1395

特别特别强调一定要弄明白树的重心的含义:

https://www.luogu.com.cn/blog/Five-shifts-Forever/shu-di-zhong-xin

再次复习这道题目时,一定要点开这个链接,再一次复习一下,树的重心的含义及性质,以及如何求树的重心

树的重心:

1:概念:树的重心也叫树的质心。对于一棵树n个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小。换句话说,删除这个点后最大连通块(一定是树)的结点数最小

2:性质:

      1:树中所有点到某个点的距离和中,到重心的距离和是最小的(实际应用中经常用到此性质)

      2:把两棵树通过一条边相连,新的树的重心在原来两棵树重心的连线上

      3:一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置

      4:一棵树最多有两个重心,且相邻

3:如何寻找树的重心:

      求树的重心运用动态规划的思想,也就是树上跑DP,先任选一个结点作为根节点,把无根树变成有根树,然后设d(i)表示以i为根的子树的结点的个数。不难发现d(i)=∑d(j)+1,j∈s(i)。s(i)为i结点的所有儿子结点的编号的集合。程序也十分简单:只需要DFS一次,在无根树有根数的同时计算即可,连记忆化都不需要——因为本来就没有重复计算。 那么,删除结点i后,最大的连通块有多少个呢?结点i的子树中最大有max{d(j)}个结点,i的“上方子树”中有n-d(i)个结点, 如图9-13。这样,在动态规划的过程中就可以找出树的重心了

https://img-blog.csdnimg.cn/20200205231817647.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Fpd28xMzc2MzAxNjQ2,size_16,color_FFFFFF,t_70

思路:

1:树的重点可以说是树的平衡点,其使以它为根的树中所有的子树的节点数相近

一:这道题目自己一开始解的时候就是暴力dijkstra,以每一个节点为起点,均跑一遍dijkstra,然后取表示到每一点最短距离的d[]数组的元素和最小的那一组做为最终结果,但是有3个测试点TLE,附上70分的代码

#include <bits/stdc++.h>

using namespace std;
const int maxn=5e4+1;
vector<pair<int,int> >e[maxn];
int n,mini,mi,ans,a,b,d[maxn];

void init()
{
    for(int i=1;i<=n;i++)d[i]=1e9;
}

int dijkstra(int s)
{
    init();
    ans=0;
    priority_queue<pair<int,int> >q;
    d[s]=0;
    q.push(make_pair(-d[s],s));
    while(!q.empty())
    {
        int now=q.top().second;
        q.pop();
        for(int i=0;i<e[now].size();i++)
        {
            int v=e[now][i].first;
            if(d[v]>d[now]+e[now][i].second)
            {
                d[v]=d[now]+e[now][i].second;
                q.push(make_pair(-d[v],v));
            }
        }
    }
    for(int i=1;i<=n;i++)
        ans+=d[i];
    return ans;
}

int main()
{
    ios::sync_with_stdio(0);
    cin>>n;
    for(int i=0;i<n-1;i++)
    {
        cin>>a>>b;
        e[a].push_back(make_pair(b,1));
        e[b].push_back(make_pair(a,1));
    }
    mini=1e9;
    for(int i=1;i<=n;i++)
    {
        int temp=dijkstra(i);
        if(temp<mini)
        {
            mini=temp;
            mi=i;
        }
    }
    cout<<mi<<" "<<mini<<endl;
    return 0;
}

二:参考树的重心的博客之后,按照先寻找树的重心的思路,代码如下:

1:核心步骤:寻找树的重心:按照概念:

        对于一棵树n个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小。换句话说,删除这个点后最大连通块(一定是树)的结点数最小

void dfs(int s,int f)
{
    d[s]=1;
    int res=0;
    for(int i=0;i<g[s].size();i++)
    {
        if(g[s][i]==f)continue;
        dfs(g[s][i],s);
        d[s]+=d[g[s][i]];
        res=max(res,d[g[s][i]]);
    }
    res=max(res,n-d[s]);
    if(res<inf||(res==inf&&ans>s))
    {
        inf=res;
        ans=s;
    }
}

解释这段核心代码:

1:为什么传两个参数,因为vector<int> g[maxn]存的是与一个节点相连的所有节点,这里是把图看作树,找的是以一个节点的孩子节点为根节点的子树中所包含的最大节点数,因此 if(g[s][i]==f)continue;表示跳过其父亲节点

2:dfs(1,0);一开始传第一个节点作为根节点时,是假设它就是根节点,因此它没有父亲节点,因此这一步if(g[s][i]==f)continue;对第一个节点不会出现;

3:d[s]=1;被认为是根节点的节点本身也占一个节点

4:res=max(res,d[g[s][i]]);以孩子节点为根的子树中含节点个数最大的子树节点个数

5:res=max(res,n-d[s]);s的“上方子树”中有n-d(s)个结点

2:然后bfs

#include <bits/stdc++.h>

using namespace std;
const int maxn=5e4+1;
int n,ans,a,b,sum,d[maxn],dis[maxn];
int inf=1e9;
vector<int> g[maxn];
queue<int> q;
bool vis[maxn];

void dfs(int s,int f)
{
    d[s]=1;
    int res=0;
    for(int i=0;i<g[s].size();i++)
    {
        if(g[s][i]==f)continue;
        dfs(g[s][i],s);
        d[s]+=d[g[s][i]];
        res=max(res,d[g[s][i]]);
    }
    res=max(res,n-d[s]);
    if(res<inf||(res==inf&&ans>s))
    {
        inf=res;
        ans=s;
    }
}

int main()
{
    ios::sync_with_stdio(0);
    cin>>n;
    for(int i=0;i<n-1;i++)
    {
        cin>>a>>b;
        g[a].push_back(b);
        g[b].push_back(a);
    }
    dfs(1,0);
    q.push(ans);
    while(!q.empty())
    {
        int e=q.front();
        q.pop();
        vis[e]=true;
        sum+=dis[e];
        for(int i=0;i<g[e].size();i++)
        {
            if(!vis[g[e][i]])
            {
                q.push(g[e][i]);
                dis[g[e][i]]=dis[e]+1;
            }
        }
    }
    printf("%d %d",ans,sum);
    return 0;
}

三:1:核心步骤同二,找树的重心

        2:dijkstra

#include <bits/stdc++.h>

using namespace std;
const int maxn=5e4+1;
int n,ans,a,b,sum,d[maxn],dis[maxn];
int inf=1e9;
vector<pair<int,int> > e[maxn];

void init()
{
    for(int i=1;i<=n;i++)dis[i]=1e9;
}

void dfs(int s,int f)
{
    d[s]=1;
    int res=0;
    for(int i=0;i<e[s].size();i++)
    {
        if(e[s][i].first==f)continue;
        dfs(e[s][i].first,s);
        d[s]+=d[e[s][i].first];
        res=max(res,d[e[s][i].first]);
    }
    res=max(res,n-d[s]);
    if(res<inf||(res==inf&&ans>s))
    {
        inf=res;
        ans=s;
    }
}

int dijkstra(int s)
{
    init();
    priority_queue<pair<int,int> >q;
    dis[s]=0;
    q.push(make_pair(-dis[s],s));
    while(!q.empty())
    {
        int now=q.top().second;
        q.pop();
        for(int i=0;i<e[now].size();i++)
        {
            int v=e[now][i].first;
            if(dis[v]>dis[now]+e[now][i].second)
            {
                dis[v]=dis[now]+e[now][i].second;
                q.push(make_pair(-dis[v],v));
            }
        }
    }
    for(int i=1;i<=n;i++)
        sum+=dis[i];
    return sum;
}

int main()
{
    ios::sync_with_stdio(0);
    cin>>n;
    for(int i=0;i<n-1;i++)
    {
        cin>>a>>b;
        e[a].push_back(make_pair(b,1));
        e[b].push_back(make_pair(a,1));
    }
    dfs(1,0);
    dijkstra(ans);
    printf("%d %d",ans,sum);
    return 0;
}
发布了117 篇原创文章 · 获赞 37 · 访问量 6589

猜你喜欢

转载自blog.csdn.net/aiwo1376301646/article/details/104190214