【问题描述】
小C上周末和他可爱的同学小A一起去X湖玩。
X湖景区一共有n个景点,这些景点由n-1条观光道连接着,从每个景点开始都可以通过观光道直接或间接地走到其他所有的景点。小C带着小A从1号景点开始游玩。游览完第一个景点后,先由小C决定下一个游览的景点,他们一起走去那个景点玩。接下来,他们轮流决定他们下一步去哪个景点玩。他们不会选择已经走过的景点,因为重复游览一个景点是无趣的。当他们无法选择下一个景点时,他们就结束旅程。
小C是好动的男孩纸,所以他希望游览的过程尽量长,也就是走过观光道的长度和最大。而小A是文静的女孩纸,她希望游览的过程尽量短。小A和小C都极度聪明,且他们的目光都足够长远,他们做出的决策都是对自己最优的。由于小C在旅游前就仔细研究了X湖景区的地图,他可以在旅行开始前就用自己惊人的数学能力推算出他和小A旅行的路径长度。
小C的梦境是美好的。在他的梦里,他和小A又进行了n-1次旅行,第i次旅行从i+1号点开始,每次也是小C先决定下一个景点,然后小A,然后小C……直到旅行结束。现在小C希望你对于所有n次旅行,求出他和小A旅行的路径长度。
【输入格式】
第一行一个正整数n,表示景点的个数。
接下来n-1行,每行三个正整数u,v,c。表示有一条连接u和v的双向观光道,其长度为c。
【输出格式】
输出一共N行,每行一个正整数。第i行表示从i号点开始旅行他们走过的路径长度。
【输入输出样例】
travel_sample1.in
5
1 2 1
1 3 2
2 4 3
2 5 4
travel_sample1.out
4
4
7
6
7
【样例解释】
从1号景点开始:
若小C选择走到3号景点,则小A无法选择下一个景点,旅行的路径长度为2
若小C选择走到2号景点,则小A会在4号景点和5号景点中选择更近的4号点,然后小C无法选择下一个景点,旅行结束,旅行的路径长度会是4
所以小C会选择走到2号点,最终的路径长度是4
【数据范围】
对于20%的数据,N ≤ 15
对于60%的数据,N ≤ 3000
对于100%的数据,N ≤ 300000, c[i] ≤ 1e9
【60分】 O(n2) 树形DP
- 显然X湖景区是一个树形结构。原问题“不能重复”的限制等价于每次只能选择当前节点的子节点。
- 我们设
f[x]
表示小C从
x
点开始往下走能走出的最大的路径长度,
g[x] 表示小A从 x 点开始往下走能走出的最小的路径长度。则转移为:( y 的f[x]=Max(g[y]+lenx→y),g[x]=Min(f[y]+lenx→y) x 的子节点, lenx→y 表示边 x→y 的路径长度)。 - 然后我们以每个点为根,都做一遍这样的树形 DP ,复杂度为 O(n2)
【100分】 O(n) 换根 + 树形DP
- 可以发现:做很多遍树形 DP 是没有必要的,我们考虑每次 O(1) 将树根转移到一个相邻的节点上
- 设旧树根为
x
,新树根为
y ,则当我们将树根从 x 转移到y 上时:其实 f 和g 数组中只需要修改 x 和y 的值。 - 那么如何修改呢?实际上我们并不需要真的去修改
f,g
数组,我们另外记
h0[y],h1[y]
,分别表示从点
y
开始往上走能走出的最小和最大的路径长度,那么此时若以
y 为根,新的 f′[y]=Max(h1[y],f[y]),g′[y]=Min(h0[y],g[y]) - 考虑
h0[y],h1[y]
如何转移,这里以
h0[y]
为例,可以分两种情况讨论:(以下
x
为
y 的父节点)
[1]、若 f[x] 是由 f[y] 转移过来, h0[y] 只能由 h1[x] 或 f[x] 的次大值(记为 fs[x] )转移过来,也就是h0[y]=Max(h1[x],fs[x])+lenx→y(f[y]+lenx→y=f[x])[2]、若 f[x] 不是由 f[y] 转移过来, h0[y] 能由 h1[x] 或 f[x] 转移过来,也就是h0[y]=Max(h1[x],f[x])+lenx→y(f[y]+lenx→y≠f[x]) - 因此我们在最开始做的那遍树形 DP 中也要记录 f,g 的次优值 fs,gs ,之后再对树遍历一遍计算 h0,h1 即可,总复杂度为 O(n)
【代码】
//f[x][0]表示次优值,f[x][1]表示最优值,g数组同理
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const ll Maxn = 1000000000000000000ll;
const int N = 3e5 + 5;
ll f[N][2], g[N][2], h[N][2];
int n;
struct Edge
{
int to, cst; Edge *nxt;
}p[N << 1], *T = p, *lst[N];
inline ll Max(const ll &x, const ll &y) {return x > y ? x : y;}
inline ll Min(const ll &x, const ll &y) {return x < y ? x : y;}
inline void addEdge(const int &x, const int &y, const int &z)
{
(++T)->nxt = lst[x]; lst[x] = T; T->to = y; T->cst = z;
(++T)->nxt = lst[y]; lst[y] = T; T->to = x; T->cst = z;
}
inline void Dfs1(const int &x, const int &fa)
{
g[x][0] = g[x][1] = Maxn;
for (Edge *e = lst[x]; e; e = e->nxt)
{
int y = e->to, z = e->cst;
if (y == fa) continue;
Dfs1(y, x);
if (g[y][1] + z > f[x][1])
f[x][0] = f[x][1], f[x][1] = g[y][1] + z;
else if (g[y][1] + z > f[x][0])
f[x][0] = g[y][1] + z;
if (f[y][1] + z < g[x][1])
g[x][0] = g[x][1], g[x][1] = f[y][1] + z;
else if (f[y][1] + z < g[x][0])
g[x][0] = f[y][1] + z;
}
if (g[x][1] == Maxn) g[x][1] = 0;
}
inline void Dfs2(const int &x, const int &fa)
{
for (Edge *e = lst[x]; e; e = e->nxt)
{
int y = e->to, z = e->cst;
if (y == fa) continue;
h[y][0] = Max(h[x][1], (g[y][1] + z == f[x][1] ? f[x][0] : f[x][1])) + z;
h[y][1] = Min(h[x][0], (f[y][1] + z == g[x][1] ? g[x][0] : g[x][1])) + z;
Dfs2(y, x);
}
}
inline int get()
{
char ch; int res = 0;
while ((ch = getchar()) < '0' || ch > '9');
res = ch - '0';
while ((ch = getchar()) >= '0' && ch <= '9')
res = (res << 3) + (res << 1) + ch - '0';
return res;
}
inline void put(ll x)
{
if (x > 9) put(x / 10);
putchar(x % 10 + 48);
}
int main()
{
freopen("travel.in", "r", stdin);
freopen("travel.out", "w", stdout);
n = get(); int u, v, x;
for (int i = 1; i < n; ++i)
{
u = get(); v = get();
addEdge(u, v, get());
}
Dfs1(1, 0);
for (Edge *e = lst[x = 1]; e; e = e->nxt)
{
int y = e->to, z = e->cst;
h[y][0] = (g[y][1] + z == f[x][1] ? f[x][0] : f[x][1]) + z;
h[y][1] = (f[y][1] + z == g[x][1] ? g[x][0] : g[x][1]) + z;
//这里注意要特别处理根的子节点情况
Dfs2(y, x);
}
for (int i = 1; i <= n; ++i)
put(Max(f[i][1], h[i][1])), putchar('\n');
}