传送门: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。
输出格式
一行一个整数,表示最小的不方便值。
题解
-
结构体和变量定义:
Edge
结构体:用于存储边信息,包括边的终点v
,下一条边的索引next
,以及边的权重w
。h[]
数组:存储每个节点的邻接链表的头节点索引。cnt
:边的计数器,用于给每条边分配唯一的索引。n
:节点数量。sum
:所有节点的权值之和。dis[]
数组:存储以节点1为根时,每个节点的子树中所有带权节点到该节点的距离之和。f[]
数组:存储每个节点作为目标节点时,所有带权节点到该节点的距离之和。C[]
数组:存储每个节点的权值。Q[]
数组:存储以节点1为根时,每个节点的子树中所有节点的权值之和。maxx
:用于记录最小的总距离。
-
add(int u, int v, int w)
函数:- 用于添加边。由于是无向图,所以需要添加两条边,分别是从
u
到v
和从v
到u
。
- 用于添加边。由于是无向图,所以需要添加两条边,分别是从
-
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为根的子树的权重之和。
-
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]);
用于更新最小总距离。
-
main()
函数:- 读取节点数量
n
和每个节点的权值C[i]
。 - 读取边信息,并调用
add()
函数添加边。 - 调用
dfs1(1, 1)
函数计算以节点1为根时的子树信息。 - 将
f[1]
初始化为dis[1]
,并将maxx
初始化为f[1]
。 - 调用
dfs2(1, 1)
函数计算以每个节点作为目标节点时的总距离,并更新maxx
。 - 输出最小总距离
maxx
。
- 读取节点数量
算法思路
-
问题转化:
- 将问题转化为:找到树上的一个节点,使得所有带权节点到该节点的带权距离之和最小。
-
两次DFS:
- 第一次 DFS (
dfs1
):- 计算以某个固定节点(通常是节点1)为根时,每个节点的子树中所有带权节点到该节点的距离之和 (
dis[]
)。 - 计算以某个固定节点(通常是节点1)为根时,每个节点的子树的权重之和 (
Q[]
)。
- 计算以某个固定节点(通常是节点1)为根时,每个节点的子树中所有带权节点到该节点的距离之和 (
- 第二次 DFS (
dfs2
):- 利用第一次DFS的结果,通过状态转移方程计算以每个节点作为目标节点时的总距离 (
f[]
)。 - 在计算过程中,更新最小总距离
maxx
。
- 利用第一次DFS的结果,通过状态转移方程计算以每个节点作为目标节点时的总距离 (
- 第一次 DFS (
核心思想
- 动态规划:通过
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;
}