[USACO10MAR] Great Cow Gathering G

[USACO10MAR] Great Cow Gathering G

题目描述

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

每个奶牛居住在 N N N 个农场中的一个,这些农场由 N − 1 N-1 N1 条道路连接,并且从任意一个农场都能够到达另外一个农场。道路 i i i 连接农场 A i A_i Ai B i B_i Bi,长度为 L i L_i Li。集会可以在 N N N 个农场中的任意一个举行。另外,每个牛棚中居住着 C i C_i Ci 只奶牛。

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

输入格式

第一行一个整数 N N N

第二到 N + 1 N+1 N+1 行:第 i + 1 i+1 i+1 行有一个整数 C i C_i Ci

N + 2 N+2 N+2 行到 2 N 2N 2N 行:第 i + N + 1 i+N+1 i+N+1 行为 3 3 3 个整数: A i , B i A_i,B_i Ai,Bi L i L_i Li

输出格式

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

样例 #1

样例输入 #1

5 
1 
1 
0 
0 
2 
1 3 1 
2 3 2 
3 4 3 
4 5 3

样例输出 #1

15

提示

1 ≤ N ≤ 1 0 5 1\leq N\leq 10^5 1N105 1 ≤ A i ≤ B i ≤ N 1\leq A_i\leq B_i\leq N 1AiBiN 0 ≤ C i , L i ≤ 1 0 3 0 \leq C_i,L_i \leq 10^3 0Ci,Li103

依次枚举每一个农场作为集会地点(根节点),求出最小代价,其中的最小值就是问题的答案。

定义状态:d[i]表示以i为根的子树中的牛到节点i所需要的最小代价。

状态转移:设j是i的孩子节点,cnt[j]是子树j中牛的数量,w(i, j)是i->j的边的权值。

① 子树j中的牛如果要到达i,需要先到j,代价为d[j],再从j到i,代价为:cnt[j] *w(i,j);

② 状态转移方程:d[i] = sum(d[j] + cnt[j] * w(i, j)); 问题:单次计算时间复杂度O(N + M),总时间复杂度O(N * (N+M))会超时。

新增状态:f[i]表示以i作为整个树的根(聚会地点)时的最小代价,答案ans = min(f[i]);

性能优化:假设以节点1为根,已经计算出了所有的d[i]: 1. f[1] = d[1] 2. 考虑非1号节点以外的任意节点v,设u是以1为根的树中v的父节点,如下图所示,假设f[u]已求出,考虑如何求解f[v],f[v]可以由两部分组成:

① 以1为根的树中子树v中的节点走过来的,最小代价为d[v]。

② 从u走过来的牛,这些牛要先走到u,再从u走到v,设sum为牛的总数。a) 有sum-cnt[v]头牛会先走到u,代价为:f[u]-d[v]-cnt[v]*w(u,v). b) 接下来这些牛要从u->v,代价为:(sum-cnt[v])*w(u,v)

③ 可得:f[v] = f[u] + (sum - 2*cnt[v]) * w(u, v);

f[1]可以直接求得,接下来就可以按照dfs的顺序依次次计算出所有的f[i]。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100005
#define ll long long
using namespace std;
ll cnt[N],c[N],f[N],g[N],sum,ans=0x3f3f3f3f3f3f3f3f;
struct Edge{
	int to ,nxt,v;
}edge[N*2];
int hd[N],tot;
int n;
void add(int u , int v , int d){
	edge[++tot].to=v;
	edge[tot].v=d;
	edge[tot].nxt=hd[u];
	hd[u]=tot;
}
void dp(int x,int fa){
	cnt[x]=c[x];
	for(int i=hd[x];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==fa) continue;
		dp(v,x);
		cnt[x]+=cnt[v];
		f[x]+=f[v]+cnt[v]*edge[i].v;
	}
}
void dfs(int x,int fa){
	for(int i = hd[x]; i; i = edge[i].nxt){
		int v = edge[i].to;
		if(v == fa) continue;
		g[v]=g[x]+(sum-2*cnt[v])*edge[i].v;
		dfs(v,x);
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&c[i]);
		sum += c[i];
	}
	for(int i=1;i<n;i++){
		int u,v,d;
		scanf("%d%d%d",&u ,&v,&d);
		add(u,v,d);
		add(v,u,d);
	}
	dp(1,0);
	g[1]=f[1];
	dfs(1,0);
	for(int i=1;i<=n;i++) ans=min(ans,g[i]);
	cout<<ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值