1 条题解

  • 0
    @ 2026-9-26 18:08:16

    模拟赛 T2,但是模拟赛题面是无根树。由于不会这个题爆炸了。

    给出一棵节点数为 nn 的有根叶向树,根节点为 11。你需要在某些节点放置任意数量的元素,每个元素可以被分配给距离 ≤k \le k 的节点,且最多分配给 ss 个不同节点。要求每个节点至少被分配到一个元素。最小化放置元素的总数目并输出。

    n≤105n \le 10^5,k≤20k \le 20,1 秒,125 MB。

    叶向树上的问题,考虑自底向上贪心。

    我们声称,元素应当尽量放置在深度较小的节点上。对于节点 xx,若其子树中仍存在距离 =k=k 的点 uu 未分配,则必须在 xx 处放置元素并分配给 uu。

    可以用调整法说明上述策略的正确性。假设存在最优解,将 xx 替换为在其子树中的点 vv,并将 vv 的元素分配给 uu。记 d(u,v)d(u,v) 为两点间距离,则 d(u,v)<kd(u,v)<k。

    现在将 vv 上的元素移动到 xx,我们要证明这仍是一个最优解,即说明被 vv 覆盖的元素可以被 xx 覆盖:

    • 对 uu 点:根据定义 d(x,u)=kd(x,u)=k,必要性成立;
    • 对 xx 子树外:假定 vv 处元素还覆盖了 xx 子树外的节点 ww,则满足 d(v,w)=d(v,x)+d(x,w)≤kd(v,w)=d(v,x)+d(x,w) \le k。移动到 xx 有 d(x,w)≤kd(x,w) \le k,故移动不会影响 xx 外的节点;
    • 对 xx 子树内:反证法,若存在 yy 使得其原本能被 vv 覆盖,但不能被 xx 覆盖,则其满足 d(v,y)≤k∧d(x,y)>kd(v,y )\le k \land d(x,y) > k。根据定义,d(x,u)=kd(x,u)=k 且 uu 是子树内最深的未分配节点,而这里 yy 比 uu 深,矛盾。所以不存在这样的 yy。

    说明这一做法的正确性后,考虑设计树形 DP 来执行策略。

    记 fi,xf_{i,x} 为在 xx 子树内,距离 xx 为 ii 的可分配数,gi,xg_{i,x} 为子树内距离为 ii 的需求数目。DP 转移时,讨论在 xx 处新增元素,并且在 xx 处将余量 fi,xf_{i,x} 与需要 gj,xg_{j,x} 进行匹配。

    一种错误的策略为:将满足 i+j≤ki+j \le k 的所有余量—需求关系在当前 LCA 全部匹配。这种贪心会将极好的余量分配给容易满足的需求,从而导致较难满足的需求只能通过放置元素来解决。

    事实上,我们只应该在如下两种情况时进行匹配:

    1. 在 i+j∈{k−1,k}i+j \in \{k-1,k\} 时将其匹配。因为如果再往祖先移动,就有 i←i+1,j←j+1i \gets i+1,j \gets j+1,此时 i+j>ki+j>k 失去匹配机会;
    2. 存在 gk,x>0g_{k,x}>0 时,再向上走就无法处理这一需求,所以必须在 xx 处新建元素 f0,xf_{0,x} 将其匹配。

    正确性容易说明,考虑上述过程相当于贡献延后 DP,且所有余量—需求关系最终都会在根节点正确处理。

    自底向上 DFS,对于当前节点 xx 有:

    • 首先,将儿子的需求上传到 xx,距离增加 11,并在 xx 处新增一个距离为 00 的需求:
      • $f_{i+1,x}=\sum\limits_{v \in \operatorname{son}(u)} f_{i,v}$;
      • $g_{i+1,x}=\sum\limits_{v \in \operatorname{son}(u)} g_{i,v}$;
      • g0,x=1g_{0,x}=1。
    • 接下来处理第一类匹配。倒序枚举 i=k,k−1,⋯ ,0i = k,k-1,\cdots,0,并考虑 j∈{k−i,k−i−1}j \in \{k-i,k-i-1\}。匹配的数目由 min⁡(gi,u,fj,u)\min(g_{i,u},f_{j,u}) 决定。
    • 在处理 i=ki=k 后,进行第二类匹配。计算出放置元素的数目 a=⌈gk,us⌉a=\lceil \frac{g_{k,u}}{s} \rceil,然后清空 gk,u←0g_{k,u} \gets 0,并将剩余元素加入 f0,uf_{0,u}。

    注意在根节点处,我们需要进行剩余匹配。首先将所有 i+j≤ki+j \le k 的 gi,ug_{i,u} 和 fj,uf_{j,u} 匹配后,求出剩余需求数目 aa,将 ⌈as⌉\lceil \frac{a}{s} \rceil 加入答案。

    按照上述过程实现,可以做到 O(nk)O(nk)。

    :::::info[代码]

    const int N = 1e5 + 5;
    int n, s, k;
    vector<int> e[N];
    i64 ans, f[21][N], g[21][N];
    void dfs(int u, int fa) {
        for (int v : e[u]) { 
            if (v == fa) continue;
            dfs(v, u);
            for (int i = 0; i < k; i++) f[i + 1][u] += f[i][v], g[i + 1][u] += g[i][v];
        }
        g[0][u] = 1;
        for (int i = k; i >= 0; i--) {
            for (int j : {k - i, k - i - 1}) {
                if (j < 0) continue;
                i64 a = min(g[i][u], f[j][u]);
                g[i][u] -= a, f[j][u] -= a;
            }
            if (i == k && g[k][u] > 0) {
                i64 a = (g[k][u] + s - 1) / s;
                ans += a, f[0][u] += a * s - g[k][u], g[k][u] = 0;
            }
        }
    }
    
    void _main() {
        cin >> n >> s >> k;
        for (int i = 1, u, v; i < n; i++) {
            cin >> u >> v;
            e[u].emplace_back(v), e[v].emplace_back(u);
        }
        dfs(1, -1);
        for (int i = k; i >= 0; i--) {
            for (int j = k - i; j >= 0; j--) {
                i64 a = min(g[i][1], f[j][1]);
                g[i][1] -= a, f[j][1] -= a;
            }
        }
        i64 rem = 0;
        for (int i = 0; i <= k; i++) rem += g[i][1];
        cout << ans + (rem + s - 1) / s;
    } 
    

    :::::

    • 1

    [POI 2009] GAS-Fire Extinguishers灭火器

    信息

    ID
    2770
    时间
    1000ms
    内存
    64MiB
    难度
    9
    标签
    递交数
    10
    已通过
    6
    上传者