1 条题解

  • 0
    @ 2025-8-24 22:36:53

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar 一扶苏一
    休息结束。邮箱 yifusuyi@qq.com

    搬运于2025-08-24 22:36:53,当前版本为作者最后更新于2022-03-12 20:44:03,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    G. 染色

    题意:给定一棵 nn 个节点的树,要求用 mm 种颜色将其染色,要求相邻两个节点的颜色不同,且任何一种颜色的使用次数都不超过 n3+1\lfloor\frac{n}{3}\rfloor + 1

    1n1001 \leq n \leq 100mn106m \leq n \leq 10^6

    关键词:容斥,DP。

    参考难度:蓝/紫。

    解析:本题出题灵感来源于 「CF1487G String Counting」和「LuoguP5664 [CSP-S2019] Emiya 家今天的饭」。

    首先注意到 n3+1\lfloor\frac{n}{3}\rfloor + 1 的限制,可以发现即使我们不考虑这个限制,也至多会有两种颜色超出(因为一旦有三种颜色超出则总数大于 nn)。于是不难设计容斥:答案 = 不考虑限制的方案数 - 至少有一种颜色超出限制的方案数 + 两种颜色超出限制的方案数。

    我们直接考虑计算两种颜色超出限制的方案数:不难设计 dp:设 fu,p,q,i,j,0/1/2f_{u, p,q,i, j, 0/1/2} 表示 ppqq 是我们钦定的两种可能超限制的颜色,以 uu 为根的子树里有 iipp 颜色和 jjqq 颜色,且 uu 是颜色 pp /颜色 qq /其他颜色的方案数,转移暂时不表。

    进一步考虑发现颜色之间没有本质区别也就是对于不同的 p,qp, q,算出 fu,p,q,i,j,0/1/2f_{u, p, q, i, j, 0/1/ 2} 的结果都是一样的,于是我们只需要计算一次 ff 数组,然后乘上 Cm2C_m^2 即可。于是状态被优化为:设 fu,i,j,0/1/2f_{u, i, j, 0/1/2} 表示以 uu 为根的子树里有 ii 个颜色 1 和 jj 个颜色 2,且 uu 是颜色 1/颜色 2/其他颜色的方案数。

    方便起见,我们设已经转移了 uu 的前 p1p - 1 个孩子,目前考虑合并第 pp 个孩子 vv 的信息。已合并的孩子的信息存储在 hp,i,j,0/1/2h_{p, i, j, 0/1/2} 数组中。

    $$h_{p, i, j, 0} = \sum_{x = 0}^i\sum_{y = 0}^j(f_{v,x,y,0} + f_{v,x,y,1}) \times h_{p,i - x,j - y,0} $$

    hp,i,j,1h_{p, i, j, 1} 的转移与之完全对称。

    考虑 hp,i,j,2h_{p, i, j, 2}:此时 uu 是一个非颜色 1 或颜色 2 的其他颜色。vv 是颜色 1/2 的转移是简单的,考虑 vv 也是一个其他颜色时,vv 的颜色不能和 uu 的相同。因此 vvhph_p 的贡献不是 fv,?,?,2f_{v, ?,?,2} 全部,而是除去了 vv 的颜色与 uu 相同的那部分。注意到 fvf_{v} 共表示了 vv(m2)(m - 2) 种颜色的方案数,而颜色之间没有本质区别,因此每种颜色的方案数都是 fv,?,?,2m2\frac {f_{v, ?, ?, 2}}{m - 2}。去掉 uu 的颜色,共有 m3m - 3 种颜色产生了贡献,因此总的贡献是 (m3)fv,?,?,2m2\frac{(m - 3)f_{v, ?, ?, 2}}{m - 2}

    于是转移如下:

    $h_{p, i, j, 2} = \sum_{x= 0}^i\sum_{y = 0}^j(f_{v,x,y,0} + f_{v,x,y,1} + \frac{(m - 3)f_{v,x,y,2}}{m - 2}) \times h_{p - 1, i - x, j - y, 2}$

    因为是模意义下的计数,所以除以 m2m - 2 要改为乘 m2m - 2 的逆。但是如果将 fu,?,?,2f_{u, ?, ?, 2} 的方案数定义为某一个不会超出限制的“颜色 3”的方案数,则可以规避掉乘逆元的过程。

    上述转移的边界条件是 hp,1,0,0=hp,0,1,1=1h_{p, 1, 0, 0} = h_{p, 0, 1, 1} = 1hp,0,0,2=m2h_{p,0,0,2} = m - 2,其余均为 00。若想要规避求逆元,则令 hp,0,0,2=1h_{p, 0, 0, 2} = 1 即可,最后计数时要把 (m2)(m - 2) 再乘上。

    不难发现,$\sum_{i = lim + 1}^n\sum_{j = 1}^nf_{1, i, j, 0/1/2}$ 即为至少有一个超出限制的方案数,其中 lim=n3+1lim = \lfloor\frac{n}{3}\rfloor + 1

    ii 改为从 11nn 枚举,就是不考虑限制的方案数。

    当然,不考虑限制的方案数也可以这样推导:

    gu,ig_{u, i} 表示以 uu 为根的子树,uu 的颜色是 ii 的方案数;GuG_u 表示以 uu 为根的子树的总方案数,则:

    $G_{u} = \sum\limits_{col = 1}^m\prod\limits_{v \in son(u)} \sum\limits_{1 \leq j \leq m}^{j \neq col}g_{v,j} = \sum\limits_{col = 1}^m \prod\limits_{v \in son(u)} \frac{m - 1}{m}G_v = m\prod\limits_{v \in son(u)} \frac{m - 1}{m}G_v$

    这里同样用到了颜色没有本质不同。

    考虑 ffO(n3)O(n^3) 个状态,转移是 O(n2)O(n^2) 的,所以看起来的时间复杂度是 O(n5)O(n^5)。但是可以使用如下的技巧将其实际复杂度优化至 O(n4)O(n^4)

    void dfs(const int u, const int f) {
      sz[u] = 1;
      for (auto v : e[u]) if (v != f) {
        dfs(v);
        for (int i = 1; i <= sz[u]; ++i) {
          for (int x = 1; x <= sz[v]; ++x) {
            for (int j = 1; j <= sz[u]; ++j) {
              for (int y = 1; y <= sz[v]; ++y) {
                // do sth O(1).
              }
            }
          }
        }
        sz[u] += sz[v];
      }
    }
    

    这是因为枚举 iixx 的过程可以看作枚举 uu 前面的子树和 vv 这个子树里面的点组成的点对,显然整棵树的点对只有 O(n2)O(n^2) 个,每个点对只会在 LCA 处被枚举。所以遍历树上 nn 个点并枚举子树间的点对的总复杂度是 O(n2)O(n^2),而里面两层循环也是 O(n2)O(n^2),故总复杂度是 O(n4)O(n^4)

    如果因为常数极小,O(n5)O(n^5) 的算法有可能卡过本题。在测试中,五次方算法的最坏用时在 800ms 左右,因此本题的时间限制开到了 500ms。事实上显而易见,里面两层循环有极小的常数,所以 n4n^4 也是一个比较松的上界。

    (C++)

    #include <array>
    #include <vector>
    #include <iostream>
    #include <cstdio>
    
    typedef long long int ll;
    
    const int maxn = 105;
    const int maxm = 1000006;
    const int p = 998244353;
    
    int n, m, invm, invm2;
    std::array<std::array<std::array<std::array<std::array<long long, 3>, maxn>, maxn>, 2>, maxn> f;
    std::array<int, maxn> pos, sz;
    std::array<long long, maxn> g;
    std::array<std::vector<int>, maxn> e;
    
    void dfs(const int u, const int fa);
    void calc(const int u, const int fa);
    
    int mpow(ll x, int y) {
      ll ret = 1;
      while (y) {
        if (y & 1) (ret *= x) %= p;
        y >>= 1;
        (x *= x) %= p;
      }
      return ret;
    }
    
    int main() {
      std::ios::sync_with_stdio(false);
      std::cin.tie(0);
      std::cin >> n >> m;
      invm = mpow(m, 998244351);
      invm2 = mpow(m - 2, 998244351);
      long long ans = m;
      for (int i = 1, u, v; i < n; ++i) {
        std::cin >> u >> v;
        e[u].push_back(v);
        e[v].push_back(u);
      }
      calc(1, 0);
      ll s1 = 0, s2 = 0;
      for (int i = n / 3 + 2; i <= n; ++i) {
        for (int j = 0; j <= n; ++j) {
          for (int k = 0; k <= 2; ++k) {
            (s1 += f[1][pos[1]][i][j][k]) %= p;
          }
        }
        for (int j = n / 3 + 2; j <= n; ++j) {
          for (int k = 0; k <= 2; ++k) {
            (s2 += f[1][pos[1]][i][j][k]) %= p;
          }
        }
      }
      ans = g[1];
      ans -= m * s1 % p;
      ans += (m - 1ll) * m / 2 % p * s2 % p;
      (ans += p) %= p;
      std::cout << ans << '\n';
    }
    
    void calc(const int u, const int fa) {
      f[u][pos[u]][1][0][0] = f[u][pos[u]][0][1][1] = 1; f[u][pos[u]][0][0][2] = m - 2;
      sz[u] = 1;
      g[u] = m;
      for (auto v : e[u]) if (v != fa) {
        calc(v, u);
        pos[u] ^= 1;
        (g[u] *= (m - 1ll) * invm % p * g[v] % p) %= p;
        for (int i = 0; i <= sz[u] + sz[v]; ++i) {
          for (int j = 0; j <= sz[u] + sz[v]; ++j) {
            f[u][pos[u]][i][j].fill(0);
          }
        }
        for (int i = sz[u]; i >= 0; --i) {
          for (int x = 0; x <= sz[v]; ++x) {
            for (int j = sz[u]; j >= 0; --j) {
              for (int y = 0; y <= sz[v]; ++y) {
                (f[u][pos[u]][i + x][j + y][0] += (f[u][pos[u] ^ 1][i][j][0]) * (f[v][pos[v]][x][y][1] + f[v][pos[v]][x][y][2]) % p) %= p;
                (f[u][pos[u]][i + x][j + y][1] += (f[u][pos[u] ^ 1][i][j][1]) * (f[v][pos[v]][x][y][0] + f[v][pos[v]][x][y][2]) % p) %= p;
                (f[u][pos[u]][i + x][j + y][2] += (f[u][pos[u] ^ 1][i][j][2]) * ((f[v][pos[v]][x][y][0] + f[v][pos[v]][x][y][1]) % p 
                    + f[v][pos[v]][x][y][2] * invm2 % p * (m - 3) % p) % p) %= p;
              }
            }
          }
        }
        sz[u] += sz[v];
      }
    }
    
    • 1

    信息

    ID
    7516
    时间
    500ms
    内存
    512MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者