[BZOJ3242][NOI2013]快餐店-基环树-动态规划

本文介绍了一种解决基环树直径问题的有效算法。通过巧妙地处理环上的节点和树状结构,利用树形DP思想,有效地计算出最优解。文章详细解释了算法的实现过程,并提供了完整的代码示例。

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

快餐店

Description

小T打算在城市C开设一家外送快餐店。送餐到某一个地点的时间与外卖店到该地点之间最短路径长度是成正比的,小T希望快餐店的地址选在离最远的顾客距离最近的地方。 快餐店的顾客分布在城市C的N 个建筑中,这N 个建筑通过恰好N 条双向道路连接起来,不存在任何两条道路连接了相同的两个建筑。任意两个建筑之间至少存在一条由双向道路连接而成的路径。小T的快餐店可以开设在任一建筑中,也可以开设在任意一条道路的某个位置上(该位置与道路两端的建筑的距离不一定是整数)。 现给定城市C的地图(道路分布及其长度),请找出最佳的快餐店选址,输出其与最远的顾客之间的距离。

Input

第一行包含一个整数N,表示城市C中的建筑和道路数目。
接下来N行,每行3个整数,Ai,Bi,Li(1≤i≤N;Li>0),表示一条道路连接了建筑Ai与Bi,其长度为Li 。

Output

仅包含一个实数,四舍五入保留恰好一位小数,表示最佳快餐店选址距离最远用户的距离。
注意:你的结果必须恰好有一位小数,小数位数不正确不得分。

Sample Input

1 2 1
1 4 2
1 3 2
2 4 1

Sample Output

2.0

HINT

数据范围

对于 10%的数据,N<=80,Li=1;
对于 30%的数据,N<=600,Li<=100;
对于 60% 的数据,N<=2000,Li<=10^9;
对于 100% 的数据,N<=10^5,Li<=10^9


蒟蒻第一眼望去,是个可做的DP,但是超级难写……
然后看题解,第一篇的想法与咱相似,简单而粗暴。
然而,这位dalao写了300+行,而且还是压了行的……
接着第二篇的dalao60行随手水过……而且只是在咱的思路基础上加了一些巧妙的东西就O(n)了……

(╯‵□′)╯︵┻━┻


思路:

很明显答案是原图直径/2……

蒟蒻版:
用dp预处理出环上所有节点到其子树中最远点的距离,答案在环上的情况分左半边和右半边用单调队列扫一遍整个环找最大的子树最远点距离,树上则在一开始预处理时顺手爆枚答案。
显然复杂度还好但是代码实现起来丧心病狂……

dalao版:
所谓基环树,即有且仅有一个环的树,或者说,n个点n条边的树。
那么我们拆一条环上的边它不就变成树了?
那这不就是树形DP吗,还需要什么单调队列……╮(╯﹏╰)╭

那么,同样先用dp预处理环上每个节点到其子树中点的最远距离。
然后随意选一条边断开,选断开的边的其中一头为起点,O(n)在环上扫一遍。
扫的时候,dp处理出每个环上节点(包括它自己)到起点路径上某一节点子树中的最远点到起点的距离,以及该点到起点的路径上的某两个环上节点(包括它自己)的子树最远点之间的距离中的最大值,分别存下来。

然后,把起点和扫描的方向反过来,再做一遍上面的DP。

让咱来描述一下目前为止这样做的意义:
如果咱要找到这棵基环树直径,显然得是从环上一棵子树中的最远点到另一棵子树。
那么如果咱记录下某点到起点和终点的路径上子树最远点距起点或终点最远的距离,把它们加起来然后再加上断开的边的距离,不就是这个点经过断开边的情况下的答案吗?
但同时由于咱上面处理的是必须经过断开的边的情况下的,那么咱必须再处理不经过的情况,所以有了第二部分的DP——这部分的意义还是很明显的(因为这部分记录的值能直接更新答案)。

所以我们的最后一步便是:O(n)扫一遍整个环,令正向下标为1,逆向下标为2,子树最远点到起点的距离为u,子树最远点到其之前最远的某子树最远点距离记为v,断开的边的长为len,有DP方程:
ans=min{ans,max{max{v1[i],v2[i+1]},u1[i]+u2[i+1]+len}

翻译一下就是:
选某点为起点,答案取:
当前节点到起点之间路径上每对节点的子树最远点之间的距离的最大值;
当前节点+1处节点到终点之间路径上每一对节点的子树最远点之间的距离的最大值;
当前节点到起点路径上某点的子树最远点到起点的距离最大值,加上当前节点+1到终点路径上某点的子树最远点到终点的最大值,再加上断开的边的长度;
的最大值。

这正好涵盖了所有的情况……吗?
事实上,还有所有答案全在同一棵子树上的情况我们还没处理。
然而,这种情况显然可以在预处理时顺手统计……

同时,咱求的是直径,不是最终答案,因此要除以2。

所以就这样愉快地AC了!ヽ( ̄▽ ̄)ノ

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

typedef long long ll;

const int N=100009;

int n;
int fa[N],fae[N],dfn[N],dfs_clock;
int c[N],isc[N],ccnt;
int to[N<<1],nxt[N<<1],beg[N],tot;
ll clen[N],w[N<<1],f[N],u1[N],v1[N],u2[N],v2[N],ans;

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9'){x=x*10+(ch^48);ch=getchar();}
    return x;
}

inline void adde(int u,int v,int ww)
{
    to[++tot]=v;
    nxt[tot]=beg[u];
    w[tot]=ww;
    beg[u]=tot;
}

inline void add(int u,int v,int w)
{
    adde(u,v,w);adde(v,u,w);
}

inline void dfs(int u)
{
    dfn[u]=++dfs_clock;
    for(int i=beg[u],v;i;i=nxt[i])
    {
        if((v=to[i])==fa[u])continue;

        if(dfn[v] && dfn[v]>dfn[u])
        {
            int now=v;
            while(now!=u)
            {
                isc[now]=1;
                c[++ccnt]=now;
                clen[ccnt]=fae[now];
                now=fa[now];
            }
            isc[u]=1;
            c[++ccnt]=u;
            clen[ccnt]=w[i];
        }
        else if(!dfn[v])
        {
            fa[v]=u;
            fae[v]=w[i];
            dfs(v);
        }
    }
}

inline void dfs2(int u,int fas)
{

    for(int i=beg[u],v;i;i=nxt[i])
    {
        if((v=to[i])!=fas && !isc[v])
        {
            dfs2(v,u);
            ans=max(ans,f[u]+f[v]+w[i]);
            f[u]=max(f[u],f[v]+w[i]);
        }
    }
}

int main()
{
    n=read();
    for(int i=1,u,v,ww;i<=n;i++)
    {
        u=read();
        v=read();
        ww=read();
        add(u,v,ww);
    }

    ans=0;
    dfs(1);

    for(int i=1;i<=ccnt;i++)
        dfs2(c[i],0);

    ll sum=0,mx=0;
    for(int i=1;i<=ccnt;i++)
    {
        sum+=clen[i-1];u1[i]=max(u1[i-1],f[c[i]]+sum);
        v1[i]=max(v1[i-1],f[c[i]]+mx+sum);
        mx=max(mx,f[c[i]]-sum);
    }

    ll llen=clen[ccnt];
    clen[ccnt]=0;
    sum=mx=0;

    for(int i=ccnt;i>=1;i--)
    {
        sum+=clen[i];u2[i]=max(u2[i+1],f[c[i]]+sum);
        v2[i]=max(v2[i+1],f[c[i]]+mx+sum);
        mx=max(mx,f[c[i]]-sum);
    }

    ll mn=v1[ccnt];
    for(int i=1;i<ccnt;i++)
        mn=min(mn,max(max(v1[i],v2[i+1]),u1[i]+u2[i+1]+llen));
    ans=max(ans,mn);

    printf("%.1lf\n",(double)ans/2);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值