题目大意
传送中世界树是一棵非常巨大的树,它有着许许多多的枝条以及节点,每个节点上都有一个平台。
好不容易来到传说中的世界树下,小新当然要爬上去看看风景。
小新每经过一条边都会耗费体力值。然而世界树之主想给他弄些麻烦,于是他在每条边上都设了一个魔法阵,当小新踏上那条边时会被传送回根节点,魔法阵只生效一次。这岂不是要累死小新?幸运的是,每个平台上都有无数个加速器,这些加速器可以让小新在当前节点所连的边上耗费的体力值减少,不同平台的加速器性能不一定相同,但同一个平台的加速器性能绝对相同。世界树之主给了小新一次“换根”的机会,他可以将世界树的任何一个节点变为根,但所有的边都不能改变。
小新想问你,将根换为哪个节点能使小新爬到世界树上的每个节点耗费的体力值和最少。
默认编号为 \(1\) 的点为初始根。
对于 \(100\%\) 的数据,\(0<n<=700000\)
解题思路
比较显然的换根 DP
。
对于题目所说的魔法阵,一种可行的解释时,假设你要走到一个终点,再传送回根节点,你可以走到那个点时,把那个点通向的所有边都踩一下,再传回根节点,这样就不需要考虑魔法阵的问题了。
然后是换根 DP
的基本思路。
先求出以 \(1\) 为根时的总贡献。
然后将以点 \(1\) 为根时的总贡献传递到 \(1\) 的儿子中,然后一直传递。
设当前点为 \(u\),\(v\) 为 \(u\) 的儿子,\(dp_i\) 为以点 \(i\) 为根时的总贡献。
则有一下转移方程(自己画个图推一下就出来了):
上面的式子中,\(w_i\) 为 \(u\) 到 \(v\) 耗费的体力值,\(w_{i \ xor \ 1}\) 即为 \(v\) 到 \(u\) 耗费的体力值。(因为正向边与反向边的编号相差 \(1\),但要注意,编号要从 \(2\) 开始)。
AC CODE
#include <bits/stdc++.h>
#define int long long
#define _ (int) 2e6 + 5
using namespace std;
int n;
int ans = LLONG_MAX, id;
int a[_];
int tot = 1, head[_], to[_ << 1], nxt[_ << 1], w[_ << 1];
int dp[_], dep[_], path[_], cnt[_], kkk[_], siz[_];
void add(int u, int v, int val)
{
to[++tot] = v;
nxt[tot] = head[u];
w[tot] = val;
head[u] = tot;
}
void dfs(int u, int fa, int d)
{
dep[u] = d;
siz[u] = 1;
for(int i = head[u]; i; i = nxt[i])
{
int v = to[i], dis = w[i];
if(v == fa) continue;
path[v] = path[u] + dis;
dfs(v, u, d + 1);
siz[u] += siz[v];
dp[1] += path[v];
}
}
void dfss(int u, int fa)
{
for(int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if(v == fa) continue;
dp[v] = dp[u] - siz[v] * w[i] + (siz[1] - siz[v]) * w[i xor 1];
dfss(v, u);
}
}
signed main()
{
scanf("%lld", &n);
for(int i = 1; i <= n; ++i)
{
scanf("%lld", &a[i]);
}
for(int i = 1; i < n; ++i)
{
int u, v, w;
scanf("%lld%lld%lld", &u, &v, &w);
add(u, v, w - a[u]);
add(v, u, w - a[v]);
}
dfs(1, 0, 0);
dfss(1, 0);
for(int i = n; i >= 1; --i)
{
if(ans >= dp[i])
{
ans = dp[i];
id = i;
}
}
printf("%lld\n%lld\n", id, ans);
return 0;
}