1 条题解

  • 0
    @ 2025-12-10 20:57:17

    「雅礼集训 2018 Day10」文明 题解

    问题重述

    给定一棵 nn 个节点的树,进行 qq 次游戏。每次游戏有 kk 个玩家,初始位置分别为 a1,a2,,aka_1, a_2, \dots, a_k(黈力是 11 号玩家,初始位置为 a1a_1)。玩家轮流操作,每轮操作将自己的领土向外扩展 11 的距离(占领所有相邻的未占领节点)。问黈力最终能占领多少个节点。

    问题转化

    通过分析游戏过程,可以得出以下关键性质:

    1. 扩展速度:所有玩家每轮都以相同速度(距离 11)扩展

    2. 竞争规则:节点 vv 最终被玩家 ii 占领当且仅当:

      • d(v,ai)=minjd(v,aj)d(v, a_i) = \min_{j} d(v, a_j)(距离最小)
      • 在距离相同的玩家中,编号最小的获胜(黈力是 11 号,有优先级)
    3. 最终状态:每个节点属于离它最近的初始点,距离相同时属于编号最小的玩家

    因此,问题转化为:在树上为每个节点分配离它最近的"首都" aia_i,距离相同时选择编号小的,统计黈力(a1a_1)占领的节点数

    算法思路

    核心观察

    设黈力的初始点为 rtrt(代码中的变量名),其他玩家的初始点为 vexvex。对于任意节点 vv

    1. 如果 vvrtrt 的距离 \leq 到其他所有 vexvex 中点的距离,则 vv 属于黈力
    2. 否则属于其他玩家

    这等价于:黈力占领的区域是以 rtrt 为中心的"Voronoi 区域",边界由到 rtrt 和到其他点距离相等的点构成。

    关键引理

    对于两个点 uuvv,它们的势力范围分界点 mid(u,v)mid(u,v) 定义为:

    • 如果 d(rt,u)=d(rt,v)d(rt,u) = d(rt,v),则分界点在连接路径的中点
    • 更一般地,mid(u,v)mid(u,v) 是使得 d(rt,x)=d(u,x)d(rt,x) = d(u,x) 的点 xx

    重要性质mid(u,v)mid(u,v)rtrtuu 的距离差为 d(rt,u)/2d(rt,u)/2(取整处理)。

    算法步骤

    1. 预处理

    • 使用树链剖分或倍增法预处理 LCA,支持 O(logn)O(\log n) 查询任意两点距离
    • 计算每个节点的 DFS 序、子树大小、重链等信息

    2. 对于每次查询

    • 输入 kk 个点,rt=a1rt = a_1 是黈力的首都
    • 对于每个其他玩家的点 uu
      • 计算 mid=Find(u)mid = Find(u)rtrtuu 的势力分界点
      • 在 DFS 序中标记 midmid 的子树(这些节点离 uu 更近)

    3. 势力范围计算

    SS 为所有分界点的 DFS 序集合(包括 rtrt 自身)。

    情况 1:没有祖先关系的点

    • 按 DFS 序排序,贪心计算被其他玩家占领的子树
    • 如果某个分界点的子树完全在当前已覆盖区间外,则整个子树被其他玩家占领
    • 从答案 nn 中减去这些子树的大小

    情况 2:存在分界点是 rtrt 的祖先

    • ancesancesrtrt 的最近祖先分界点
    • ancesances 的子树中,只有深度 >dep[ances]> dep[ances] 的部分可能属于黈力
    • 需要特殊处理 ancesances 的父节点方向的部分

    核心函数解析

    Find(u) 函数

    计算 rtrtuu 的势力分界点:

    inline int Find(int u) {
        int lca = LCA(rt, u);
        int dist = dep[u] + dep[rt] - 2 * dep[lca];
        int k = (dist - 1) / 2;  // 距离除以2下取整
        
        if (k <= dep[u] - dep[lca])
            return Anc(u, k);    // 在u到lca的路径上
        else
            return Anc(rt, dist - k);  // 在rt到lca的路径上
    }
    

    势力范围计算逻辑

    1. 收集所有分界点:对于每个其他玩家点 uu,计算 Find(u)Find(u) 并加入集合
    2. 按 DFS 序处理
      • 如果分界点 pp 的 DFS 序区间 [dfn[p],dfn[p]+siz[p]1][dfn[p], dfn[p]+siz[p]-1] 与已覆盖区间不重叠
      • 则该整个子树被其他玩家占领
    3. 处理祖先关系
      • 如果存在分界点是 rtrt 的祖先
      • 需要减去 ancesances 的父节点方向的节点(这些节点离祖先更近)

    时间复杂度分析

    • 预处理O(nlogn)O(n \log n)
    • 每次查询
      • 计算 k1k-1 个分界点:O(klogn)O(k \log n)
      • 集合操作:O(klogk)O(k \log k)
      • 总复杂度:O(klogn)O(k \log n)
    • 总复杂度:$O(n \log n + \sum k_i \log n) \leq O(n \log n + 10^6 \log n)$,可以通过

    代码实现要点

    1. 树链剖分:用于快速求 LCA 和树上距离
    2. 倍增法Anc(u, x) 函数用于求 uuxx 级祖先
    3. DFS 序性质:子树在 DFS 序中是连续区间
    4. 集合维护:使用 std::set 维护分界点的 DFS 序

    正确性证明

    关键引理证明

    对于任意节点 vv

    • 如果 vvmid(u,rt)mid(u,rt) 的子树中且不在更近的其他分界点子树中,则 d(v,rt)>d(v,u)d(v,rt) > d(v,u)
    • 因此 vv 被玩家 uu 占领
    • 反之,如果 vv 不在任何分界点的子树中,则 d(v,rt)d(v,ui)d(v,rt) \leq d(v,u_i) 对所有 uiu_i 成立

    贪心正确性

    按 DFS 序处理时,由于子树区间是连续的,且区间要么包含要么不相交,可以使用贪心选择:

    • 如果当前区间起点大于已覆盖区间的终点,说明这是一个新的不相交子树
    • 该子树全部被其他玩家占领

    样例验证

    以题目样例第一组数据为例:

    • n=6n=6k=2k=2rt=1rt=1u=3u=3
    • 计算 Find(3)Find(3)dist(1,3)=1dist(1,3)=1k=0k=0,返回 33
    • 分界点集合:{1,3}\{1,3\}
    • 33 的子树大小为 33(节点 3,5,63,5,6
    • 但这些节点中,3333 更近,5,65,633 比离 11 更近
    • 黈力占领节点:1,2,41,2,4,共 33

    总结

    本题通过将游戏过程转化为树上最近邻分类问题,利用树链剖分和 DFS 序的性质,高效计算黈力的势力范围。核心在于理解势力分界点的计算和处理祖先关系的特殊情况。算法时间复杂度优秀,能够处理 n,q5×105n,q \leq 5\times 10^5ki106\sum k_i \leq 10^6 的大规模数据。

    • 1

    信息

    ID
    6053
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    2
    已通过
    1
    上传者