1 条题解

  • 0
    @ 2025-10-29 17:41:08

    一、题意理解

    我们有一棵 nn 个节点的树,要给每个节点分配一个 [1,m][1,m] 的整数权值,要求对于每条边 (u,v)(u,v),有 xuxvk|x_u - x_v| \ge k

    求合法的染色方案数,对 109+710^9+7 取模。


    二、初步分析

    这是一个典型的图上染色计数问题,但约束是相邻节点数值差至少为 kk,而不是传统的不同颜色。

    • k=0k=0 时,约束不存在,方案数 = mnm^n
    • kmk \ge m 时,除非 m=1m=1k=0k=0,否则无解?不对,kmk \ge m 时,相邻节点数值差至少 mm,而数值范围 [1,m][1,m],差最大 m1m-1,所以只有 m=1m=1k=0k=0 才可能,否则无解。但 k100k \le 100mm 可能很大,所以 kmk \ge m 不会出现(因为 m1m \ge 1k100k \le 100mm 可能到 10910^9,所以 kk 肯定小于 mm)。

    三、小数据做法(m100m \le 100

    对于 n100n \le 100m100m \le 100,我们可以用树形 DP

    dp[u][c]dp[u][c] 表示以 uu 为根的子树,节点 uu 染颜色 cc 时,子树的方案数。

    转移: $ dp[u][c] = \prod_{v \in \text{son}(u)} \sum_{c'=1,\, |c-c'|\ge k}^m dp[v][c'] $

    初始化:叶子节点 dp[u][c]=1dp[u][c] = 1

    答案: ans=c=1mdp[root][c] \text{ans} = \sum_{c=1}^m dp[\text{root}][c]

    复杂度 O(nm2)O(n m^2),在 n,m100n,m \le 100 时可行。


    四、mm 很大时的优化

    mm 很大时,m109m \le 10^9,不能枚举颜色。

    观察约束 xuxvk|x_u - x_v| \ge k,即 xvxukx_v \le x_u - kxvxu+kx_v \ge x_u + k

    如果我们固定节点 uu 的颜色 cc,那么 vv 的合法颜色是: [1,ck][c+k,m] [1, c-k] \cup [c+k, m] 注意边界处理。


    1. 关键观察

    对于固定的 ccvv 的合法颜色数量只与 cc 有关,并且是分段常数函数。

    f(c)=f(c) = vv 的合法颜色数量(即 c[cck]\sum_{c'} [|c-c'|\ge k])。

    计算:

    • ckc \le k,则 [c+k,m][c+k, m] 长度 m(c+k)+1=mck+1m-(c+k)+1 = m-c-k+1,且 [1,ck][1, c-k] 为空(因为 ck0c-k \le 0),所以 f(c)=mck+1f(c) = m-c-k+1
    • k<cmkk < c \le m-k,则 [1,ck][1, c-k] 长度 ckc-k[c+k,m][c+k, m] 长度 mck+1m-c-k+1,所以 f(c)=(ck)+(mck+1)=m2k+1f(c) = (c-k) + (m-c-k+1) = m-2k+1
    • c>mkc > m-k,则 [c+k,m][c+k, m] 为空,[1,ck][1, c-k] 长度 ckc-k,所以 f(c)=ckf(c) = c-k

    所以: $ f(c) = \begin{cases} m-c-k+1, & 1 \le c \le k \\ m-2k+1, & k < c \le m-k \\ c-k, & m-k < c \le m \end{cases} $


    2. 利用函数的分段性质

    在树形 DP 中,dp[u]dp[u] 可以看作一个定义在 [1,m][1,m] 上的函数,值是一个整数(方案数)。

    转移是: $ dp[u](c) = \prod_{v \in \text{son}(u)} \left( \sum_{c' \in \text{valid}(c)} dp[v](c') \right) $ 其中 valid(c)=[1,ck][c+k,m]\text{valid}(c) = [1, c-k] \cup [c+k, m]


    3. 区间和优化

    我们只需要维护每个 dp[u]dp[u]前缀和 Su(x)=i=1xdp[u](i)S_u(x) = \sum_{i=1}^x dp[u](i)

    那么: c[1,ck]dp[v](c)=Sv(ck) \sum_{c' \in [1, c-k]} dp[v](c') = S_v(c-k) $ \sum_{c' \in [c+k, m]} dp[v](c') = S_v(m) - S_v(c+k-1) $ 所以: $ \sum_{c' \in \text{valid}(c)} dp[v](c') = S_v(c-k) + S_v(m) - S_v(c+k-1) $

    于是转移可以写成: $ dp[u](c) = \prod_{v \in \text{son}(u)} \big[ S_v(c-k) + S_v(m) - S_v(c+k-1) \big] $


    4. 关键点压缩

    虽然 mm 很大,但 n100n \le 100k100k \le 100,所以 dp[u](c)dp[u](c) 是分段多项式(每段是常数或线性函数),并且段的分界点只与 kk 有关。

    事实上,dp[u](c)dp[u](c)cc 的不同区间上是低次多项式(次数不超过子树大小)。

    我们可以只计算 dp[u]dp[u]O(nk)O(nk) 个关键点上的值,然后插值得到整段的值。


    更简单的方法:注意到 dp[u](c)dp[u](c) 在区间 [k+1,mk][k+1, m-k] 上是常数(因为 f(c)f(c) 是常数),在两端是线性函数。

    所以我们可以只计算:

    • c=1,2,,2kc = 1, 2, \dots, 2k
    • c=m2k+1,,mc = m-2k+1, \dots, m(如果 mm 很大,这部分可以用一个偏移量表示)

    mm 可能很大,我们无法存下所有 cc,但我们可以用生成函数多项式来表示 dp[u]dp[u]


    5. 多项式方法

    Fu(x)=c=1mdp[u](c)xcF_u(x) = \sum_{c=1}^m dp[u](c) x^{c} 是生成函数。

    转移: $ F_u(x) = \sum_{c=1}^m \left[ \prod_{v} \big( S_v(c-k) + S_v(m) - S_v(c+k-1) \big) \right] x^{c} $ 这看起来复杂,但 Sv(c)=[x1..c]Fv(x)S_v(c) = [x^{1..c}] F_v(x) 是前缀和。

    实际上更简单:我们只关心 Sv(m)S_v(m)SvS_vO(k)O(k) 个位置的值。


    6. 离散化关键点

    由于 nnkk 小,dp[u](c)dp[u](c)c[1,2k][m2k,m]c \in [1, 2k] \cup [m-2k, m] 之外是常数,所以我们可以只计算 O(k)O(k)cc 的值,然后乘以区间长度。

    具体:

    • c=1..2kc = 1..2k 直接计算。
    • c=m2k+1..mc = m-2k+1..m 直接计算(如果 mm 很大,这部分可以用 c=mcc' = m-c 对称处理)。
    • 中间大段 [2k+1,m2k][2k+1, m-2k]dp[u](c)dp[u](c) 是常数,记为 VV,这段长度 L=m4kL = m - 4k(如果 m>4km > 4k),贡献 VLV \cdot L

    这样我们只需计算 O(k)O(k) 个点的 dpdp 值,复杂度 O(nk2)O(n k^2)


    五、算法步骤(对大数据)

    1. 如果 m4km \le 4k,直接枚举所有 cc 做树形 DP。
    2. 否则:
      • 对每个节点 uu,维护两个数组 low[1..2k]low[1..2k]high[1..2k]high[1..2k],分别表示 c=1..2kc=1..2kc=m2k+1..mc=m-2k+1..m 时的 dp[u](c)dp[u](c),以及一个常数 midmid 表示中间段的 dp[u](c)dp[u](c)
      • 转移时,对每个 cc 计算 v(Sv(ck)+Sv(m)Sv(c+k1))\prod_{v} (S_v(c-k) + S_v(m) - S_v(c+k-1)),其中 Sv(x)S_v(x)low,mid,highlow, mid, high 计算:
        • 如果 x2kx \le 2kSv(x)=i=1xlow[i]S_v(x) = \sum_{i=1}^x low[i]
        • 如果 2k<xm2k2k < x \le m-2kSv(x)=Sv(2k)+mid(x2k)S_v(x) = S_v(2k) + mid \cdot (x - 2k)
        • 如果 x>m2kx > m-2k,$S_v(x) = S_v(m-2k) + \sum_{i=m-2k+1}^x high[i - (m-2k)]$。
      • 注意 Sv(m)S_v(m) 是总方案数。
    3. 最终答案是 Sroot(m)S_{\text{root}}(m)

    六、代码框架(C++)

    这里给出 m100m \le 100 的直接 DP 实现(适用于测试点 1,2):

    #include <algorithm>
    #include <iostream>
    #include <vector>
    #include <string>
    #include <bitset>
    #include <array>
    
    using namespace std;
    
    array<array<long long, 20001>, 101>   DParr;
    array<vector<int>, 101>              tree;
    array<long long, 101>                answers;
    const int                           moder = 1e9 + 7;
    
    inline long long rgsum(int curnd, int rgl, int rgr) {
        if (rgl > rgr)
            return 0;
    
        if (rgl < 1)
            rgl = 1;
    
        return (DParr[curnd][rgr] - DParr[curnd][rgl - 1] + moder) % moder;
    }
    
    void DFS_dp(int curnd, int father, const int mind, const int maxv) {
        DParr[curnd].fill(0);
    
        for (int i = 1; i <= min(20000, maxv); i++)
            DParr[curnd][i] = 1;
    
        for (int i : tree[curnd]) {
            if (i == father)
                continue;
    
            DFS_dp(i, curnd, mind, maxv);
    
            if (maxv <= 19900)
                for (int j = 1; j <= 19900; j++)
                    (DParr[curnd][j] *= answers[i] - rgsum(i, j - mind + 1, j + mind - 1) + moder) %= moder;
            else {
                for (int j = 1; j <= 9901; j++)
                    (DParr[curnd][j] *= answers[i] - rgsum(i, j - mind + 1, j + mind - 1) + moder) %= moder;
    
                for (int j = 9902; j <= 20000; j++)
                    if (maxv - j + 1 > 9901)
                        DParr[curnd][j] = DParr[curnd][9901];
                    else
                        DParr[curnd][j] = DParr[curnd][maxv - j + 1];
            }
        }
    
        for (int i = 1; i <= 20000; i++)
            (DParr[curnd][i] += DParr[curnd][i - 1]) %= moder;
    
        if (maxv <= 20000)
            answers[curnd] = DParr[curnd][20000] % moder;
        else
            answers[curnd] = (DParr[curnd][9900] * 2 + (maxv - 19800) * (DParr[curnd][9901] - DParr[curnd][9900] + moder)
                              % moder) % moder;
    }
    
    int main(int argc, char *argv[], char *envp[]) {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int testcnt;
        cin >> testcnt;
    
        while (testcnt--) {
            for (int i = 1; i <= 100; i++)
                tree[i].clear();
    
            int cnt, mind, maxv;
            cin >> cnt >> maxv >> mind;
    
            for (int i = 1; i < cnt; i++) {
                int node1, node2;
                cin >> node1 >> node2;
                tree[node1].push_back(node2), tree[node2].push_back(node1);
            }
    
            DFS_dp(1, 0, mind, maxv);
            cout << answers[1] << '\n';
        }
    
        return 0;
    }
    

    七、总结

    本题的关键在于:

    1. 理解相邻节点数值差约束的树形染色计数。
    2. 对小 mm 直接树形 DP。
    3. 对大 mm 利用 dp[u](c)dp[u](c) 的分段常数/线性性质,压缩状态数。
    4. 通过维护前缀和快速计算转移。
    • 1

    信息

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