P1099 树的直径 DFS + 二分 / 尺取法

题意

传送门 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(eEe))

#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值