2024 ICPC 武汉邀请赛 E. Boomerang【动态维护树直径、LCA求树上两点距离】

E. Boomerang

传送门:https://codeforces.com/gym/105143/problem/E

题意

给定一个 n n n 个节点的树,根节点为 r r r , 设时刻 t t t 的点集 V ( r , t ) = { v    ∣    d i s ( r , v ) ≤ t } V(r, t) = \{v \; | \; dis(r, v) \leq t \} V(r,t)={vdis(r,v)t}
其中 d i s ( u , v ) dis(u, v) dis(u,v) 表示树上两点 u u u v v v 间的唯一简单路径的边数。
再给定一个 t 0 t_0 t0,对每个 k ∈ [ 1 , n ] k \in [1,n] k[1,n],你需要选择一个点 r 0 r_0 r0,定义时刻 t t t 的点集 V ′ ( r 0 , t ) = { v    ∣    d i s ( r 0 , v ) ≤ k ( t − t 0 ) } V ^\prime (r_0, t) = \{v \; | \; dis(r_0, v) \leq k(t−t_0) \} V(r0,t)={vdis(r0,v)k(tt0)}
找到一个最小的 t t t,使得 V ( r , t ) ⊆ V ′ ( r 0 , t ) V(r, t) \subseteq V ^\prime (r_0, t) V(r,t)V(r0,t)

谣言在时刻 0 0 0 从点 r r r 开始传播,每个单位时间往外扩散一个点; 而辟谣在时刻 t 0 ( t 0 > 0 ) t_0 (t_0 > 0) t0(t0>0) 从任意点 r 0 r_0 r0 开始传播,传播速度为 k k k
现在需要对于每个 k k k,找到最早的辟谣完全覆盖谣言时间点( r 0 r_0 r0 任选)。

思路

注意到,当谣言传播到时刻 t t t 时,这是一颗子树 V ( r , t ) V(r,t) V(r,t),且所有节点距离 r r r 的距离不超过 t t t,如果我们要在当前时刻覆盖所有的谣言的话,我们选择的那个开始辟谣的点 r r r,一定要最小化它到最远的点的距离,也就是说,我们 r r r 一定选在子树 V ( r , t ) V(r,t) V(r,t)直径中点,此时距离最远的点最近,最大距离即为: ⌈ D 2 ⌉ \lceil \frac{D}{2} \rceil 2D D D D 为子树 V ( r , t ) V(r,t) V(r,t) 直径

我们从 t 0 t_0 t0 开始辟谣,那么此时辟谣了 t − t 0 t - t_0 tt0 秒,而我们要覆盖所有的谣言点,那么起码需要的辟谣速度 k = ⌈ ⌈ D 2 ⌉ t − t 0 ⌉ k =\lceil \dfrac{\lceil \dfrac{D}{2} \rceil }{t - t_0} \rceil k=tt02D。如果我们能从 t = 1 → t = t 0 + n t = 1 \rarr t = t_0 + n t=1t=t0+n 枚举 t t t,获得当时的子树 V ( r , t ) V(r, t) V(r,t),并算出那时候覆盖子树所需的最小的辟谣速度 k k k,我们就能对这些 a n s k = m i n { t ∣ k = ⌈ ⌈ D t 2 ⌉ t − t 0 ⌉ } ans_k = min \{ t \mid k =\lceil \dfrac{\lceil \dfrac{D_t}{2} \rceil }{t - t_0} \rceil\} ansk=min{tk=tt02Dt⌉},取 m i n min min

注意到子树扩散过程中,计算出来的 k k k 不一定覆盖 [ 1 , n ] [1,n] [1,n],其实随着 k k k 的增加, a n s [ k ] ans[k] ans[k]非递增的,道理很简单,如果在时刻 t t t 我们选取速度 k k k 可以覆盖这个时刻的子树的话,我们选取速度 k + 1 k + 1 k+1 只会更快覆盖这颗子树,所以我们只需要取 a n s k = m i n { a n s k , a n s k − 1 } ans_k = min \{ ans_k, ans_{k - 1} \} ansk=min{ansk,ansk1}

现在问题在于:如果动态加点,维护树的直径?注意到我们随着 t t t 的增大,每次加入的点都是新扩散出去的,也就是当前子树的某些邻居点,类似于生成树。

因此我们有以下结论:

假设此时树的直径端点为 A ,    B A ,\; B A,B,直径为 D D D,新扩散加入一个点 u u u 时,新的直径只有三种可能:

  1. A − B ,    D A-B, \; D AB,D
  2. A − u ,    d i s ( A , u ) A-u, \; dis(A,u) Au,dis(A,u)
  3. B − u ,    d i s ( B , u ) B-u, \; dis(B,u) Bu,dis(B,u)

我们只需要用 L C A LCA LCA 计算两点的距离即可

时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn)

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;

const int INF=0x3f3f3f3f;
const long long INFLL=1e18;

typedef long long ll;

const int N = 200050;

std::vector<int> g[N];
int fa[N][20];
int dep[N];

void dfs(int u, int father){
    dep[u] = dep[father] + 1;
    fa[u][0] = father;
    for(int i = 1; (1 << i) <= dep[u]; ++i)
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
    for(auto v : g[u])
        if(v ^ father)
            dfs(v, u);
}

int lca(int u, int v){
    if(dep[u] < dep[v]) std::swap(u, v);
    for(int i = 19; i >= 0; --i)
        if(dep[u] - (1 << i) >= dep[v])
            u = fa[u][i];
    if(u == v) return v;
    for(int i = 19; i >= 0; --i)
        if(fa[u][i] != fa[v][i]){
            u = fa[u][i];
            v = fa[v][i];
        }
    return fa[u][0];
}

int dis(int u, int v){
    int L = lca(u, v);
    return dep[u] + dep[v] - 2 * dep[L];
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int n;
    std::cin >> n;
    fore(i, 1, n){
        int u, v;
        std::cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    int r, t0;
    std::cin >> r >> t0;

    dfs(1, 0);

    std::vector<int> ans(n + 1, t0 + n);
    int A = r, B = r; //直径的两个端点
    int D = 0; //直径
    std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, 
                std::greater<std::pair<int, int>>> que;
    fore(i, 1, n + 1){
        if(i == r) continue;
        int d = dis(i, r);
        if(d <= t0){
            int d1 = dis(A, i), d2 = dis(B, i);
            if(D >= std::max(d1, d2)) continue;
            if(d1 > d2){
                D = d1;
                B = i;
            }
            else{
                D = d2;
                A = i;
            }
        }
        else que.push({d, i});
    }

    fore(t, t0 + 1, t0 + n + 1){
        while(!que.empty() && que.top().fi == t){
            auto [d, i] = que.top();
            que.pop();
            int d1 = dis(A, i), d2 = dis(B, i);
            // Debug(i, endl)
            if(D >= std::max(d1, d2)) continue;
            /* 更新直径 */
            if(d1 > d2){
                D = d1;
                B = i;
            }
            else{
                D = d2;
                A = i;
            }
        }
        int k = ((D + 1) / 2 + t - t0 - 1) / (t - t0);
        ans[k] = std::min(ans[k], t);
    }

    fore(i, 1, n + 1){
        ans[i] = std::min(ans[i], ans[i - 1]);
        std::cout << ans[i] << ' ';
    } 

    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值