洛谷 1099 树网的核 题解

博客详细介绍了洛谷第1099题的解题思路,这是一道大模拟题,重点在于寻找树的直径并在直径上找到满足特定条件的路径。题意包括计算点到路径的距离和路径的偏心距。解题方法是首先找到树的直径,然后枚举路径的两个端点,计算并更新最小偏心距。博客提到了数据规模较小,允许使用较简单的O(n^2)算法,并强调了几个编程细节,如使用DFS更新偏心距、避免超限、利用STL减少代码错误。

博客观赏效果更佳

第一次写这种大模拟题呢。。。觉得很考验码力和阅读理解能力,就写上了。

题意简述

给定一个带权的树,
定义:

  1. x x x到路径 P P P的距离: P P P中离 x x x最远的点的到 x x x的距离
  2. 一条路径 P P P的偏心距为:树上离路径 P P P最远的点到 P P P的距离

请找到一个路径 P P P,使得:

  1. P P P的所有点在这个树的直径上
  2. P P P中的边权和 < = S <=S <=S S S S给定
  3. P P P的偏心距最小

输出最小的偏心距。

思路

先说一下,这个题数据水甚,大概 O ( n 4 ) O(n^4) O(n4)也是能过去的,只要顾着写就可以了,几乎不用考虑时间复杂度。

首先找到直径。然后在直径上 O ( n 2 ) O(n^2) O(n2)枚举路径的两个端点,计算出这个路径的偏心距,更新答案即可。

几个细节:

  1. 偏心距就两遍 D F S DFS DFS更新一下
  2. 如果路径长度 > S >S >S,那么及时 b r e a k break break,不要继续搜了(一个大优化)
  3. 适当使用 S T L STL STL以减少代码量,增加准确率( S T L STL STL赛高!)
  4. 不要出现低级错误!!!(像我就写反了 i , j i,j i,j,还好我机智,要不然肝没了)

代码(与以前的题目不同,这个题目在于读代码)

// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 11234

    #define F(i,l,r) for(int i=l;i<=r;++i)
    #define D(i,r,l) for(int i=r;i>=l;--i)
    #define Fs(i,l,r,c) for(int i=l;i<=r;c)
    #define Ds(i,r,l,c) for(int i=r;i>=l;c)
    #define Tra(i,u) for(int i=G.Start(u);~i;i=G.Next(i))
    #define MEM(x,a) memset(x,a,sizeof(x))
    #define FK(x) MEM(x,0)
    class Graph//存图
    {
        public:
            int head[N];
            int EdgeCount;
            struct Edge
            {
                int To,Label,Next;
            }Ed[N<<3];
            void clear()
            {
                memset(head,-1,sizeof(head));
                memset(Ed,-1,sizeof(Ed));
                EdgeCount=0;
            }

            void AddEdge(int u,int v,int w=1)
            {
                ++EdgeCount;
                Ed[EdgeCount]=(Edge){v,w,head[u]};
                head[u]=EdgeCount;
            }
            void Add2(int u,int v,int w=1)
            {
                AddEdge(u,v,w);AddEdge(v,u,w);
            }

            int Start(int u)
            {
                return head[u];
            }
            int To(int u)
            {
                return Ed[u].To;
            }
            int Label(int u)
            {
                return Ed[u].Label;
            }
            int Next(int u)
            {
                return Ed[u].Next;
            }

    }G;
    int n,s;
    void R1(int &x)
    {
        x=0;char c=getchar();int f=1;
        while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
        while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=(f==1)?x:-x;
    }
    void Input()
    {
        G.clear();
        R1(n),R1(s);
        F(i,1,n-1)
        {
            int u,v,w;
            R1(u),R1(v),R1(w);
            G.Add2(u,v,w);
        }
    }

    int dis[N],fa[N];
    void DFS(int u,int f)//非常普通的DFS
    {
        if (u==f) dis[u]=0,fa[u]=-1;
        else fa[u]=f;

        Tra(i,u)
        {
            int v=G.To(i);
            if (v!=f)
            {
                dis[v]=dis[u]+G.Label(i);
                DFS(v,u);
            }
        }
    }
    int dis2[N];//注意:这个要新开一个dis数组用,因为上面那个dis数组要用来判断路径长度是否<=S
    bool In[N];
    void DFS2(int u,int f)
    {
        Tra(i,u)
        {
            int v=G.To(i);
            if (!In[v] and v!=f)//注意:判断!In[v]是为了只考虑路径外面的点
            {
                dis2[v]=dis2[u]+G.Label(i);
                DFS2(v,u);
            }
        }
    }
    void Soviet()
    {
        int l,r;//直径的两端
        DFS(1,1);
        l=max_element(dis+1,dis+n+1)-dis;//max_element:返回区间内最大值出现的第一个位置
//      min_element同理,返回区间内最小值出现的第一个位置
        DFS(l,l);
        r=max_element(dis+1,dis+n+1)-dis;
//        printf("diameter: %d %d\n",l,r);
//      直径的求法:从1开始找到最远的点,就是第一个端点。再从这个端点跑到最远的点,就是第二个端点。
        int ans=0x3f3f3f3f;
        for(int i=r;~i;i=fa[i])
        {
            for(int j=i;~j;j=fa[j])//枚举路径上的两个点
            {
                if (dis[i]-dis[j]<=s)//注意,此处的dis是以r为根的意义下算的,但是fa数组是以
//              l为根的意义下算的,所以j是在i的下面的,而不是上面,所以我这里i和j写反了,大家千万不要再犯这个错误了T_T
                {
//                    printf("i=%d j=%d\n",i,j);
                    FK(In);
                    for(int k=i;k!=j;k=fa[k])
                    {
                        In[k]=1;
                    }
                    In[j]=1;//标记路径上的点

                    for(int k=i;k!=j;k=fa[k])
                    {
                        dis2[k]=0;
                        DFS2(k,k);
                    }
                    dis2[j]=0;DFS2(j,j);//更新最小距离

                    ans=min(ans,*max_element(dis2+1,dis2+n+1));
                    //更新答案
                }
                else break;//注意及时break
            }
        }
        printf("%d\n",ans);
    }
    #define FlandreScarlet void
    FlandreScarlet IsMyWife()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        Input();
        Soviet();
    }
};
int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值