洛谷传送门
Atcoder传送门
题目大意
一颗 n n n个节点的二叉树,每个节点要么有两个儿子要么没有儿子。边有边权。
你从 1 1 1号节点出发,走到一个叶子节点。然后每一天,你可以从当前点走到另一个叶子。最后回到 1 1 1号节点,要求到过所有叶子并且每条边经过恰好两次。
每天的路费是你走过的路径上的边权和,你的公司会为你报销大部分路费,除了你旅行中所用路费最高的,行走路线是从叶子到叶子的那一天的路费。
求你自己最少要付多少路费?
输入输出格式
输入格式
第一行一个正整数 n n n。
以下 n − 1 n-1 n−1行, 每行两个整数 a i , v i a_i,v_i ai,vi, 第 i i i行表示 i + 1 i+1 i+1号点和 a i a_i ai之间有一条长度为 v i v_i vi的边。
输出格式
一行一个整数, 表示最少付的路费。
输入输出样例
输入样例#1:
7
1 1
1 1
2 1
2 1
3 1
3 1
输出样例#1:
4
输入样例#2:
9
1 2
1 2
3 2
3 2
5 2
5 2
7 2
7 2
输出样例#2:
6
输入样例#3:
15
1 2
1 5
3 3
4 3
4 3
6 5
5 4
5 3
6 3
3 2
11 5
11 3
13 2
13 1
输出样例#3:
15
输入样例#4:
3
1 0
1 0
输出样例#4:
0
数据范围
- 2 < N < 131 , 072 2<N<131,072 2<N<131,072
- 对于所有的 i i i, 1 ≤ a i ≤ i 1≤a_i≤i 1≤ai≤i
- 0 ≤ v i ≤ 131 , 072 0≤v_i≤131,072 0≤vi≤131,072
- v i v_i vi 为整数
- 给出的树为满二叉树
解题分析
一眼二分, 转化为判定性问题。
每条边只能经过一次, 那么我们进入一棵子树的时候就必须遍历完这棵子树内的所有叶节点才能再退出。
又因为这是一颗二叉树, 所以我们进入一棵以 x x x为根的子树的时候一定是进入 x x x的一个子树的叶节点访问到第一个在 x x x子树中被访问到的点, 最后一个被访问的点一定在另一个子树内。 所以不妨设三元组 ( x , d i n , d o u t ) (x,din,dout) (x,din,dout)表示进入 x x x的子树到达一个叶节点的距离为 d i n din din, 从 x x x子树内最后一个被访问的点到 x x x的距离为 d o u t dout dout, 现在问题就来到了如何合并 ( l s o n , d i n l , d o u t l ) (lson,din_l,dout_l) (lson,dinl,doutl)和 ( r s o n , d i n r , d o u t r ) (rson, din_r, dout_r) (rson,dinr,doutr)。
膜了一波网上的题解: 对于任何一个 ( l s o n , d i n l i , d o u t l i ) (lson,din_{l_i},dout_{l_i}) (lson,dinli,doutli), 我们都只需要找到一个 ( r s o n , d i n r j , d o u t r j ) (rson,din_{r_j},dout_{r_j}) (rson,dinrj,doutrj), 满足 d o u t l i + d i n r j ≤ dout_{l_i}+din_{r_j}\le doutli+dinrj≤二分的答案, 且 d o u t r j dout_{r_j} doutrj尽量小。这个直接用two-pointers就可以做到 O ( N ) O(N) O(N)。 同理我们考虑以 d i n r j din_{r_j} dinrj作为开头的情况, 那么找到满足条件的最小的 d o u t l i dout_{l_i} doutli就好了。
这样做在一个节点的两个儿子都是叶节点的时候就可以将两种组合方式都考虑进去, 进而在上面也可以考虑完所有的组合情况(感觉这里很妙)。 并且这样合并后的三元组集合大小 ∣ S x ∣ = 2 m i n ( ∣ S l s o n ∣ , ∣ S r s o n ∣ ) |S_x|=2min(|S_{lson}|,|S_{rson}|) ∣Sx∣=2min(∣Slson∣,∣Srson∣)的, 总的集合大小是 O ( N l o g ( N ) ) O(Nlog(N)) O(Nlog(N))的(完全二叉树可以达到)。
总复杂度是 O ( N l o g 2 ( N ) ) O(Nlog^2(N)) O(Nlog2(N))的, 但是偷懒用了 s o r t sort sort, 于是多一个 l o g log log卡卡就过去了。
代码如下:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <vector>
#include <algorithm>
#include <cstdlib>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define ls son[now][0]
#define rs son[now][1]
#define dls dis[now][0]
#define drs dis[now][1]
#define MX 200500
#define ll long long
template <class T>
IN void in(T &x)
{
x = 0; R char c = gc;
for (; !isdigit(c); c = gc);
for (; isdigit(c); c = gc)
x = (x << 1) + (x << 3) + c - 48;
}
template <class T> IN T max(T a, T b) {return a > b ? a : b;}
template <class T> IN T min(T a, T b) {return a < b ? a : b;}
FILE* fl;
int n, cnt;
ll bd;
struct INFO {ll din, dout;};
IN bool cmp1(const INFO &x, const INFO &y) {return x.dout < y.dout;}
IN bool cmp2(const INFO &x, const INFO &y) {return x.din == y.din ? x.dout < y.dout : x.din < y.din;}
struct Edge {int to, len, nex;} edge[MX << 1];
std::vector <INFO> stat[MX];
int head[MX], son[MX][2], dis[MX][2];
ll mn[MX];
IN void add(R int from, R int to, R int len)
{edge[++cnt] = {to, len, head[from]}, head[from] = cnt;}
void DFS(R int now, R int fa)
{
for (R int i = head[now]; i; i = edge[i].nex)
{
if (edge[i].to == fa) continue;
if (!ls) DFS(ls = edge[i].to, now), dls = edge[i].len;
else if (!rs) DFS(rs = edge[i].to, now), drs = edge[i].len;
}
}
void solve(std::vector <INFO> &l, std::vector <INFO> &r, std::vector <INFO> &res, R int id)
{
if (l.size() > r.size()) std::swap(l, r);
int lsiz = l.size(), rsiz = r.size(), cur;
if (!lsiz) return;
std::sort(l.begin(), l.end(), cmp1);//dout inc
std::sort(r.begin(), r.end(), cmp2);//din inc
mn[0] = r[0].dout; cur = rsiz - 1;
for (R int i = 1; i < rsiz; ++i) mn[i] = min(mn[i - 1], r[i].dout);
for (R int i = 0; i < lsiz; ++i)
{
W ((~cur) && l[i].dout + r[cur].din > bd) --cur;
if (~cur) res.push_back({l[i].din, mn[cur]});
}
for (R int i = 0; i < lsiz; ++i) std::swap(l[i].din, l[i].dout);
for (R int i = 0; i < rsiz; ++i) std::swap(r[i].din, r[i].dout);
std::sort(l.begin(), l.end(), cmp1);
std::sort(r.begin(), r.end(), cmp2);
mn[0] = r[0].dout; cur = rsiz - 1;
for (R int i = 1; i < rsiz; ++i) mn[i] = min(mn[i - 1], r[i].dout);
for (R int i = 0; i < lsiz; ++i)
{
W ((~cur) && l[i].dout + r[cur].din > bd) --cur;
if (~cur) res.push_back({mn[cur], l[i].din});
}
}
void DFS(R int now)
{
stat[now].clear();
if (!ls) return stat[now].push_back({0, 0}), void();
DFS(ls); DFS(rs);
for (R int i = stat[ls].size() - 1; ~i; --i) stat[ls][i].din += dls, stat[ls][i].dout += dls;
for (R int i = stat[rs].size() - 1; ~i; --i) stat[rs][i].din += drs, stat[rs][i].dout += drs;
solve(stat[ls], stat[rs], stat[now], now);
}
IN bool check(R ll v)
{
bd = v; DFS(1);
return stat[1].size();
}
int main(void)
{
in(n); int foo, bar;
ll lef = 0, rig = 1e10, mid, ans;
for (R int i = 2; i <= n; ++i)
in(foo), in(bar), add(foo, i, bar), add(i, foo, bar);
DFS(1, 0);
W (lef <= rig)
{
mid = lef + rig >> 1;
if (check(mid)) ans = mid, rig = mid - 1;
else lef = mid + 1;
}
printf("%lld", ans);
}