题意
传送门 P1099 树网的核
在任意一条直径上求出的最小偏心距都相等。
树上只有一条直径时显然成立。当树有多条直径,它们必定相交,且中点汇聚于同一处,那么中心附近树的各条直径的重叠部分是相同的,若直径交点在核以外,根据直径的最长性,对答案没有影响;若直径在核以内,则另一条直径所在的分叉为最长链,根据对称性可知,交换直径进行计算对答案没有影响。
树的直径 DFS + 二分
问题满足单调性,转化为判定问题,即判断是否存在一个核,满足其偏心距不超过当前二分值。那么 D F S DFS DFS 求树的直径,设直径端点为 s , t s,t s,t,根据直径的最长性,任何从直径上某一点 u u u 与 s s s 之间分叉离开直径的子树,其最远点与 u u u 的距离都不会超过 s s s 与 u u u 。
那么求出满足距离 s , t s,t s,t 不超过当前二分值的端点 l , r l,r l,r,则树的直径上 [ l , r ] [l,r] [l,r] 一段即是可能满足条件的核;还需要以 [ l , r ] [l,r] [l,r] 上各点为根节点对不包含直径(除了根节点)的子树进行 D F S DFS DFS,判断离核最远点的距离是否不超过当前二分值。满足上述条件即可收缩上界。时间复杂度 O ( n log ( ∑ e ∈ E ∣ e ∣ ) ) O\big(n\log(\sum\limits_{e\in E}|e|)\big) O(nlog(e∈E∑∣e∣))。
#include <bits/stdc++.h>
using namespace std;
#define maxn 305
#define maxw 1005
int N, S, pre[maxn], d_tr[maxn], d_dm[maxn];
int head[maxn << 1], nxt[maxn << 1], to[maxn << 1], cost[maxn << 1], tot;
bool in[maxn];
vector<int> diam;
void add(int x, int y, int z)
{
to[++tot] = y, cost[tot] = z, nxt[tot] = head[x], head[x] = tot;
}
void d_dfs(int x, int f, int &t)
{
if (d_dm[x] > d_dm[t])
t = x;
for (int i = head[x]; i; i = nxt[i])
{
int y = to[i], z = cost[i];
if (y != f)
d_dm[y] = d_dm[x] + z, pre[y] = x, d_dfs(y, x, t);
}
}
void t_dfs(int x, int f, int &t)
{
if (d_tr[x] > d_tr[t])
t = x;
for (int i = head[x]; i; i = nxt[i])
{
int y = to[i], z = cost[i];
if (y != f && !in[y])
d_tr[y] = d_tr[x] + z, t_dfs(y, x, t);
}
}
bool judge(int m, int s, int t)
{
int l = 0, r = diam.size() - 1, tt;
for (int i = 0; i < diam.size(); ++i)
if (d_dm[diam[i]] - d_dm[s] <= m)
l = i;
for (int i = diam.size() - 1; i >= 0; --i)
if (d_dm[t] - d_dm[diam[i]] <= m)
r = i;
if (r < l)
return 1;
if (d_dm[diam[r]] - d_dm[diam[l]] > S)
return 0;
for (int i = l; i <= r; ++i)
{
tt = 0, d_tr[diam[i]] = 0;
t_dfs(diam[i], 0, tt);
if (d_tr[tt] > m)
return 0;
}
return 1;
}
int main()
{
scanf("%d%d", &N, &S);
for (int i = 1; i < N; ++i)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
add(y, x, z);
}
int s = 1, t = 0;
d_dfs(s, 0, t);
s = t, t = 0, d_dm[s] = pre[s] = 0;
d_dfs(s, 0, t);
for (int i = t; i; i = pre[i])
in[i] = 1, diam.push_back(i);
reverse(diam.begin(), diam.end());
int lb = -1, ub = maxn * maxw;
while (ub - lb > 1)
{
int mid = (lb + ub) >> 1;
if (judge(mid, s, t))
ub = mid;
else
lb = mid;
}
printf("%d\n", ub);
return 0;
}
树的直径 DFS + 尺取法
设直径端点为
s
,
t
s,t
s,t,直径上节点的距离为
d
d
m
d_{dm}
ddm,以直径上的节点为根
D
F
S
DFS
DFS 求出的子树上距离根最远距离为
d
t
r
d_{tr}
dtr,那么以
u
l
,
u
r
u_l,u_r
ul,ur 为端点的树网的核偏心距为
max
(
max
k
∈
[
l
,
r
]
{
d
t
r
[
u
k
]
}
,
d
d
m
[
u
s
,
u
l
]
,
d
d
m
[
u
r
,
u
t
]
)
\max\big(\max\limits_{k\in [l,r]}\{d_{tr}[u_k]\}, d_{dm}[u_s,u_l],d_{dm}[u_r,u_t]\big)
max(k∈[l,r]max{dtr[uk]},ddm[us,ul],ddm[ur,ut]) 根据树的直径的最长性,则有
d
d
m
[
u
s
,
u
l
]
≥
max
k
∈
[
s
,
l
]
{
d
t
r
[
u
k
]
}
d_{dm}[u_s,u_l]\geq \max\limits_{k\in [s,l]}\{d_{tr}[u_k]\}
ddm[us,ul]≥k∈[s,l]max{dtr[uk]}
d
d
m
[
u
r
,
u
t
]
≥
max
k
∈
[
r
,
t
]
{
d
t
r
[
u
k
]
}
d_{dm}[u_r,u_t]\geq \max\limits_{k\in [r,t]}\{d_{tr}[u_k]\}
ddm[ur,ut]≥k∈[r,t]max{dtr[uk]} 那么偏心距可以表示为
max
(
max
k
∈
[
s
,
t
]
{
d
t
r
[
u
k
]
}
,
d
d
m
[
u
s
,
u
l
]
,
d
d
m
[
u
r
,
u
t
]
)
\max\big(\max\limits_{k\in [s,t]}\{d_{tr}[u_k]\}, d_{dm}[u_s,u_l],d_{dm}[u_r,u_t]\big)
max(k∈[s,t]max{dtr[uk]},ddm[us,ul],ddm[ur,ut]) 第一项为常量,对于第二项,显然
d
d
m
[
u
l
,
u
r
]
d_{dm}[u_l,u_r]
ddm[ul,ur] 尽可能大会使偏心距尽可能小,那么不断求出满足
d
d
m
[
u
l
,
u
r
]
≤
S
d_{dm}[u_l,u_r]\leq S
ddm[ul,ur]≤S 且
d
d
m
[
u
l
,
u
r
]
d_{dm}[u_l,u_r]
ddm[ul,ur] 尽可能大的
[
l
,
r
]
[l,r]
[l,r] 更新答案。
r
r
r 单调递增,尺取法求解即可。时间复杂度
O
(
n
)
O(n)
O(n)。
#include <bits/stdc++.h>
using namespace std;
#define maxn 305
#define inf 0x3f3f3f3f
int N, S, pre[maxn], d_tr[maxn], d_dm[maxn];
int head[maxn << 1], nxt[maxn << 1], to[maxn << 1], cost[maxn << 1], tot;
bool in[maxn];
vector<int> diam;
void add(int x, int y, int z)
{
to[++tot] = y, cost[tot] = z, nxt[tot] = head[x], head[x] = tot;
}
void d_dfs(int x, int f, int &t)
{
if (d_dm[x] > d_dm[t])
t = x;
for (int i = head[x]; i; i = nxt[i])
{
int y = to[i], z = cost[i];
if (y != f)
d_dm[y] = d_dm[x] + z, pre[y] = x, d_dfs(y, x, t);
}
}
void t_dfs(int x, int f, int &t)
{
if (d_tr[x] > d_tr[t])
t = x;
for (int i = head[x]; i; i = nxt[i])
{
int y = to[i], z = cost[i];
if (y != f && !in[y])
d_tr[y] = d_tr[x] + z, t_dfs(y, x, t);
}
}
int main()
{
scanf("%d%d", &N, &S);
for (int i = 1; i < N; ++i)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
add(y, x, z);
}
int s = 1, t = 0;
d_dfs(s, 0, t);
s = t, t = 0, d_dm[s] = pre[s] = 0;
d_dfs(s, 0, t);
for (int i = t; i; i = pre[i])
in[i] = 1, diam.push_back(i);
reverse(diam.begin(), diam.end());
int m_tr = 0, tt;
for (auto u : diam)
{
d_tr[u] = tt = 0;
t_dfs(u, 0, tt);
m_tr = max(m_tr, d_tr[tt]);
}
int l = 0, r = 0, res = inf;
for (;;)
{
while (r < diam.size() && d_dm[diam[r]] - d_dm[diam[l]] <= S)
++r;
res = min(res, max(m_tr, max(d_dm[diam[l]] - d_dm[s], d_dm[t] - d_dm[diam[r - 1]])));
if (r == diam.size())
break;
++l;
}
printf("%d\n", res);
return 0;
}