1 条题解
-
0
一、题意理解
我们有一棵 个节点的树,要给每个节点分配一个 的整数权值,要求对于每条边 ,有 。
求合法的染色方案数,对 取模。
二、初步分析
这是一个典型的图上染色计数问题,但约束是相邻节点数值差至少为 ,而不是传统的不同颜色。
- 当 时,约束不存在,方案数 = 。
- 当 时,除非 且 ,否则无解?不对, 时,相邻节点数值差至少 ,而数值范围 ,差最大 ,所以只有 且 才可能,否则无解。但 , 可能很大,所以 不会出现(因为 且 , 可能到 ,所以 肯定小于 )。
三、小数据做法()
对于 ,,我们可以用树形 DP。
设 表示以 为根的子树,节点 染颜色 时,子树的方案数。
转移: $ dp[u][c] = \prod_{v \in \text{son}(u)} \sum_{c'=1,\, |c-c'|\ge k}^m dp[v][c'] $
初始化:叶子节点 。
答案:
复杂度 ,在 时可行。
四、 很大时的优化
当 很大时,,不能枚举颜色。
观察约束 ,即 或 。
如果我们固定节点 的颜色 ,那么 的合法颜色是: 注意边界处理。
1. 关键观察
对于固定的 , 的合法颜色数量只与 有关,并且是分段常数函数。
设 从 的合法颜色数量(即 )。
计算:
- 若 ,则 长度 ,且 为空(因为 ),所以 。
- 若 ,则 长度 , 长度 ,所以 。
- 若 ,则 为空, 长度 ,所以 。
所以: $ 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](c) = \prod_{v \in \text{son}(u)} \left( \sum_{c' \in \text{valid}(c)} dp[v](c') \right) $ 其中 。
3. 区间和优化
我们只需要维护每个 的前缀和 。
那么: $ \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. 关键点压缩
虽然 很大,但 ,,所以 是分段多项式(每段是常数或线性函数),并且段的分界点只与 有关。
事实上, 在 的不同区间上是低次多项式(次数不超过子树大小)。
我们可以只计算 在 个关键点上的值,然后插值得到整段的值。
更简单的方法:注意到 在区间 上是常数(因为 是常数),在两端是线性函数。
所以我们可以只计算:
- (如果 很大,这部分可以用一个偏移量表示)
但 可能很大,我们无法存下所有 ,但我们可以用生成函数或多项式来表示 。
5. 多项式方法
设 是生成函数。
转移: $ 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} $ 这看起来复杂,但 是前缀和。
实际上更简单:我们只关心 和 在 个位置的值。
6. 离散化关键点
由于 小 小, 在 之外是常数,所以我们可以只计算 个 的值,然后乘以区间长度。
具体:
- 对 直接计算。
- 对 直接计算(如果 很大,这部分可以用 对称处理)。
- 中间大段 上 是常数,记为 ,这段长度 (如果 ),贡献 。
这样我们只需计算 个点的 值,复杂度 。
五、算法步骤(对大数据)
- 如果 ,直接枚举所有 做树形 DP。
- 否则:
- 对每个节点 ,维护两个数组 和 ,分别表示 和 时的 ,以及一个常数 表示中间段的 。
- 转移时,对每个 计算 ,其中 用 计算:
- 如果 ,。
- 如果 ,。
- 如果 ,$S_v(x) = S_v(m-2k) + \sum_{i=m-2k+1}^x high[i - (m-2k)]$。
- 注意 是总方案数。
- 最终答案是 。
六、代码框架(C++)
这里给出 的直接 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; }
七、总结
本题的关键在于:
- 理解相邻节点数值差约束的树形染色计数。
- 对小 直接树形 DP。
- 对大 利用 的分段常数/线性性质,压缩状态数。
- 通过维护前缀和快速计算转移。
- 1
信息
- ID
- 4621
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者