NOI03 逃学的小孩

问题描述

Chris家的电话铃响起了,里面传出了Chris的老师焦急的声音:喂,是Chris的家长吗?你们的孩子又没来上课,不想参加考试了吗?一听说要考试,Chris的父母就心急如焚,他们决定在尽量短的时间内找到Chris。他们告诉Chris的老师:根据以往的经验,Chris现在必然躲在朋友ShermieYashiro家里偷玩《拳皇》游戏。现在,我们就从家出发去找Chris,一但找到,我们立刻给您打电话。说完砰的一声把电话挂了。

Chris居住的城市由N个居住点和若干条连接居住点的双向街道组成,经过街道x需花费Tx分钟。可以保证,任两个居住点间有且仅有一条通路。Chris家在点CShermieYashiro分别住在点A和点BChris的老师和Chris的父母都有城市地图,但Chris的父母知道点ABC的具体位置而Chris的老师不知

为了尽快找到ChrisChris的父母会遵守以下两条规则:

l 如果A距离CB距离C近,那么Chris的父母先去Shermie家寻找Chris,如果找不到,Chris的父母再去Yashiro家;反之亦然

l Chris的父母总沿着两点间唯一的通路行走。

这题应该说是树形dp中难度较大的题了。具体参考陈瑜希的论文。

主要思想是通过两遍dfs。第一遍任意选取一个节点作为根,然后求得每个节点子树中前3远的距离。

第二遍dfs是对每个节点求其到其父亲节点引出来的其他节点的最远距离,重新调整每个节点的前3远的距离。

并且第二遍dfs有两个步骤,前一个步骤保证搜索的方向是层次的,这样就能够保证子树更新距离时,父亲节点已更新完毕。

然后为什么父亲节点会引出一条路径。其实是因为每个分叉结点的前3远节点在不同的三颗子树(以该节点为根),也就是三颗子树交于该点,而过该点从其父亲方向过来的只可能有一条,如果大于1条,则说明三颗子树交于多个点组成的线段。

然后可能有人会有疑问?如果仅有一条树链,那么三点不是不交于1点,但是因为我们枚举了所有分叉点,对于这条链上的中间那个点作为分叉点,并且按照论文中转换,也就是添加边权为0的节点,那么就符合题意了,这个在代码里赋初值时是有体现的。但可能有人又有疑问了,其他点作为分叉点,最优解不就不交于1点了。

没错,但是此时根据代码,这些点只会求得以三颗子树交于该点的最优解,因此这些点得不到最优解。但是因为枚举了所有点作为分叉点,所以必然会在以那个中间点作为分叉点时,得到最优解。

所以这些完全不需要担心。。。但是如果你有这些顾虑,那说明你思考问题相当严密,值得鼓励。


代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#define ll long long
#define Maxn 200010
using namespace std;

struct edge{
    int u,v;
    ll val;
}p[Maxn];
struct maxx{
    int ori;
    ll dis;
    bool operator<(const maxx &a)const{
        return dis>a.dis;
    }
}ma[Maxn][4];
vector<edge> dv[Maxn];
ll ans;
int vis[Maxn];
void dfs1(int u){
    vis[u]=1;
    for(int i=0;i<dv[u].size();i++){
        int v=u==dv[u][i].u?dv[u][i].v:dv[u][i].u;
        if(!vis[v]&&u!=v){
            dfs1(v);
            ma[u][3].dis=ma[v][0].dis+dv[u][i].val;
            ma[u][3].ori=v;
            sort(ma[u],ma[u]+4);
            ans=max(ans,ma[u][0].dis+2*ma[u][1].dis+ma[u][2].dis);
        }
    }
}
void dfs2(int u){
    vis[u]=1;
    for(int i=0;i<dv[u].size();i++){
        int v=u==dv[u][i].u?dv[u][i].v:dv[u][i].u;
        if(vis[v]&&u!=v){
            if(ma[v][0].ori!=u)
                ma[u][3].dis=ma[v][0].dis+dv[u][i].val;
            else
                ma[u][3].dis=ma[v][1].dis+dv[u][i].val;
            sort(ma[u],ma[u]+4);
            ans=max(ans,ma[u][0].dis+2*ma[u][1].dis+ma[u][2].dis);
            break;
        }
    }
    for(int i=0;i<dv[u].size();i++){
        int v=u==dv[u][i].u?dv[u][i].v:dv[u][i].u;
        if(!vis[v]&&u!=v) dfs2(v);
    }
}
int main()
{
    int n,m,val;
    scanf("%d%d",&n,&m);
    ans=0;
    for(int i=1;i<=n;i++) dv[i].clear();
    for(int i=0;i<m;i++){
        scanf("%d%d%lld",&p[i].u,&p[i].v,&p[i].val);
        dv[p[i].u].push_back(p[i]);
        dv[p[i].v].push_back(p[i]);
    }
    memset(vis,0,sizeof vis);
    memset(ma,0,sizeof ma);
    dfs1(1);
    memset(vis,0,sizeof vis);
    dfs2(1);
    printf("%lld\n",ans);
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值