【洛谷】【P2986】Great Cow Gathering G(树形DP)

传送门Great Cow Gathering G        树形DP


题目描述

Bessie 正在计划一年一度的奶牛大集会,来自全国各地的奶牛将来参加这一次集会。当然,她会选择最方便的地点来举办这次集会。

每个奶牛居住在 NN 个农场中的一个,这些农场由 N−1N−1 条道路连接,并且从任意一个农场都能够到达另外一个农场。道路 ii 连接农场 AiAi​ 和 BiBi​,长度为 LiLi​。集会可以在 NN 个农场中的任意一个举行。另外,每个牛棚中居住着 CiCi​ 只奶牛。

在选择集会的地点的时候,Bessie 希望最大化方便的程度(也就是最小化不方便程度)。比如选择第 XX 个农场作为集会地点,它的不方便程度是其它牛棚中每只奶牛去参加集会所走的路程之和(比如,农场 ii 到达农场 XX 的距离是 2020,那么总路程就是 Ci×20Ci​×20)。帮助 Bessie 找出最方便的地点来举行大集会。


输入格式

第一行一个整数 NN 。

第二到 N+1N+1 行:第 i+1i+1 行有一个整数 CiCi​。

第 N+2N+2 行到 2N2N 行:第 i+N+1i+N+1 行为 33 个整数:Ai,BiAi​,Bi​ 和 LiLi​。


输出格式

一行一个整数,表示最小的不方便值。


题解

  1. 结构体和变量定义

    • Edge 结构体:用于存储边信息,包括边的终点 v,下一条边的索引 next,以及边的权重 w
    • h[] 数组:存储每个节点的邻接链表的头节点索引。
    • cnt:边的计数器,用于给每条边分配唯一的索引。
    • n:节点数量。
    • sum:所有节点的权值之和。
    • dis[] 数组:存储以节点1为根时,每个节点的子树中所有带权节点到该节点的距离之和。
    • f[] 数组:存储每个节点作为目标节点时,所有带权节点到该节点的距离之和。
    • C[] 数组:存储每个节点的权值。
    • Q[] 数组:存储以节点1为根时,每个节点的子树中所有节点的权值之和。
    • maxx:用于记录最小的总距离。
  2. add(int u, int v, int w) 函数

    • 用于添加边。由于是无向图,所以需要添加两条边,分别是从 u 到 v 和从 v 到 u
  3. dfs1(int u, int ff) 函数

    • 这是一个深度优先搜索(DFS)函数,用于计算以节点1为根时,每个节点的子树的信息。
    • dis[u] 的计算:dis[u] += dis[e[i].v] + s * e[i].w; 表示节点 u 的 dis 值等于其所有子节点的 dis 值加上子节点的权值和乘以它们之间边的权重。
    • Q[u] 的计算:Q[u] = tol + C[u]; 表示节点 u 的 Q 值等于其所有子节点的 Q 值之和加上节点 u 本身的权值。
    • 返回的是以u为根的子树的权重之和。
  4. dfs2(int u, int ff) 函数

    • 这是第二个深度优先搜索(DFS)函数,用于计算以每个节点作为目标节点时的总距离。
    • f[e[i].v] = f[u] - Q[e[i].v] * e[i].w + e[i].w * (sum - Q[e[i].v]); 是状态转移方程,表示如果选择节点 e[i].v 作为目标节点,总距离等于选择节点 u 作为目标节点的总距离减去子树 e[i].v 中的节点到 u 的距离,加上剩余节点到 e[i].v 的距离。
    • maxx = min(maxx, f[e[i].v]); 用于更新最小总距离。
  5. main() 函数

    • 读取节点数量 n 和每个节点的权值 C[i]
    • 读取边信息,并调用 add() 函数添加边。
    • 调用 dfs1(1, 1) 函数计算以节点1为根时的子树信息。
    • 将 f[1] 初始化为 dis[1],并将 maxx 初始化为 f[1]
    • 调用 dfs2(1, 1) 函数计算以每个节点作为目标节点时的总距离,并更新 maxx
    • 输出最小总距离 maxx

算法思路

  1. 问题转化

    • 将问题转化为:找到树上的一个节点,使得所有带权节点到该节点的带权距离之和最小。
  2. 两次DFS

    • 第一次 DFS ( dfs1 )
      • 计算以某个固定节点(通常是节点1)为根时,每个节点的子树中所有带权节点到该节点的距离之和 ( dis[] )。
      • 计算以某个固定节点(通常是节点1)为根时,每个节点的子树的权重之和 ( Q[] )。
    • 第二次 DFS ( dfs2 )
      • 利用第一次DFS的结果,通过状态转移方程计算以每个节点作为目标节点时的总距离 ( f[] )。
      • 在计算过程中,更新最小总距离 maxx

核心思想

  • 动态规划:通过 dfs2 中的状态转移方程,利用子问题的解来求解原问题。
  • 树形结构:利用树的特殊结构,通过两次DFS来高效地计算所有可能目标节点的总距离。
#include <iostream>
using namespace std;
const int N=1e5+5;
#define ll long long
struct Edge{
    int v,next,w;
}e[2*N];
int h[2*N],cnt=1,n;
void add(int u,int v,int w){
    e[cnt]=(Edge){v,h[u],w};
    h[u]=cnt++;
}
ll sum=0;
ll dis[N],f[N];
ll C[N],Q[N];
ll maxx;
ll dfs1(int u,int ff){
    ll tol=0;
    for(int i=h[u];i;i=e[i].next){
        if(e[i].v!=ff){
            ll s= dfs1(e[i].v,u);
            dis[u]+=dis[e[i].v]+s*e[i].w;
            tol+=s;
        }
    }
    return Q[u]=tol+C[u];
}
void dfs2(int u,int ff){
    for(int i=h[u];i;i=e[i].next){
        if(e[i].v!=ff){
            f[e[i].v]=f[u]-Q[e[i].v]*e[i].w+e[i].w*(sum-Q[e[i].v]);
            maxx=min(maxx,f[e[i].v]);
            dfs2(e[i].v,u);
        }
    }
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>C[i];
        sum+=C[i];
    }
    for(int i=1;i<n;i++){
        int u,v,w;
        cin>>u>>v>>w;
        add(u,v,w);
        add(v,u,w);
    }
    dfs1(1,1);
    f[1]=dis[1];
    maxx=f[1];
    dfs2(1,1);
    cout<<maxx;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值