安吉D12-T2
- 1安吉D19 T1
- 2安吉D19 T3
- 3安吉D20 模考12总结
- 4安吉D17 T3
- 5安吉D17 T4
- 6安吉D19 模考11总结
- 7安吉D15 A
- 8安吉D16 T4
- 9安吉D17 T1
- 10安吉D17 模考10总结
- 11安吉D16 T3
- 12安吉D16 模考9总结
- 13安吉D13 模考8总结
- 14安吉D13-T2
- 15安吉D13-T3
- 16安吉D12-T3
- 17安吉D11-B
- 18安吉D11-C
- 19安吉D11-D
- 20安吉D12 模考7总结
- 21安吉D12-T2本文
- 22安吉D11-A
- 23安吉D10 模考6总结
- 24安吉D10-T3
- 25安吉D10-T4
- 26安吉D8-E
- 27安吉D9-T4
- 28安吉D8-A
- 29安吉D8-B
- 30安吉D8-C
- 31安吉D8-D
- 32安吉D9 模考5总结
- 33安吉D9-T2
- 34安吉D9-T3
- 35安吉D6-T4
- 36安吉D4-G
- 37安吉D5-T2
- 38安吉D6 模考4总结
- 39安吉D6-T1
- 40安吉D6-T3
- 41安吉-开营测试 T4
- 42安吉D5 模考3总结
- 43安吉D5-T3
- 44安吉D4-A
- 45安吉D4-B
- 46安吉D4-C
- 47安吉D4:容斥原理
- 48安吉Day4-D
- 49安吉D3 模考2总结
- 50安吉D3-T1
- 51安吉D3-T2
- 52安吉D3-T4
- 53安吉D2 模考1总结
- 54安吉D2-T2
- 55安吉D1-G
- 56安吉D1-L
- 57安吉D2-T4
原题呈现
森林基站
题目描述
熊大为了保卫狗熊岭,要在森林里建立一个由 个通信基站组成的预警网络.这 个基站由 条隐蔽的光缆连接,形成了一棵树的结构.
为了保证信号畅通,熊大需要为每个基站分配一个运行频率.频率可以选择 之间的任意整数.
然而,频率 是一个“高功率特殊频率”.由于高功率频率之间会互相干扰,且对周围低功率设备有压制作用,光头强的探测器很容易发现异常.为了安全,熊大设定了以下规则:
- 整个网络中,最多只能有 个基站被分配为频率 .
- 如果某个基站被分配了频率 ,那么与它通过光缆直接相连的所有相邻基站的频率必须严格小于 .
熊大想知道,一共有多少种合法的频率分配方案?两种方案被认为是不同的,当且仅当至少有一个基站被分配了不同的频率.
由于答案可能很大,请将结果对 取模后告诉熊大.
输入格式
输入文件名为 forest.in.
输入的第一行包含一个整数 ,表示测试数据的组数.
对于每组测试数据:
-
第一行包含四个整数 ,分别表示基站的数量、可选频率的上限、高功率特殊频率的值,以及最多允许分配频率 的基站数量.
-
接下来 行,每行包含两个整数 ,表示基站 和基站 之间有一条光缆相连.
输出格式
输出文件名为 forest.out.
对于每组测试数据,输出一行一个整数,表示合法的频率分配方案数对 取模后的结果.
样例
输入 #1
23 3 2 11 22 34 3 2 21 21 31 4输出 #1
1335样例解释
【样例 1 解释】
第 1 组测试数据: 有 个基站,频率可以选择 .高功率特殊频率是 ,最多允许有 个基站使用频率 .
- 不使用频率 :每个基站都可以选择频率 或 ,共有 种方案.
- 恰好有 个基站使用频率 :
- 若基站 频率为 ,则相邻的基站 频率必须严格小于 (只能为 ),基站 的频率可以是 或 ,共 种方案.
- 若基站 频率为 ,则相邻的基站 和 频率都必须为 ,共 种方案.
- 若基站 频率为 ,则相邻的基站 频率必须为 ,基站 的频率可以是 或 ,共 种方案.
合计共有 种合法的分配方案.
第 2 组测试数据: 有 个基站,结构为以 为中心的菊花图.频率可选 ,特殊频率是 ,最多允许 个基站使用频率 .
- 若基站 使用频率 ,则基站 必须全部使用频率 .共 种方案.
- 若基站 不使用频率 :
- 若基站 使用频率 ,则基站 可随意选择 ,但频率 的基站数不能超过 .这部分共有 种方案.
- 若基站 使用频率 ,则基站 不能使用频率 (因为使用频率 的基站,其所有邻居的频率必须严格小于 ).所以基站 只能选择频率 或 ,共 种方案.
总计 种方案.
数据范围
对于所有测试数据,保证:
- 所有测试数据的 之和不超过 .
| 测试点编号 | 特殊性质 | |||
|---|---|---|---|---|
| 1 ~ 2 | 10 | 3 | 10 | 无 |
| 3 ~ 4 | 12 | 12 | 无 | |
| 5 | 15 | 15 | 无 | |
| 6 ~ 8 | 200 | 200 | 无 | |
| 9 ~ 11 | 3000 | 10 | 无 | |
| 12 ~ 14 | 3000 | 3000 | A(菊花图) | |
| 15 ~ 17 | 3000 | 3000 | B(链) | |
| 18 ~ 20 | 3000 | 3000 | 无 |
特殊性质说明:
- A:树的形态为菊花图,即存在一个基站 ,其余所有基站都与基站 直接相连.
- B:树的形态为一条链,即每个基站的度数均不超过 .
观察到 ,可以支持 的做法.此题选用 dp.由于每一个节点的决策都只与总共存在的高功率节点和其相邻节点有关,因此,定义 dp[i][j][k] 表示考虑 为根的子树,子树内已经有了 个高功率节点(包括点 ),由于根据不同情况, 也会有不同的取值,因此最后一维 ,表示节点 频率 的取值范围所拥有的方案数:
- ,此时 .
- ,此时 .
- ,此时 .
不难发现初始状态:考虑了 0 个节点,子树已有 0 个高功率节点,共有 1 种情况.根据树形背包的相关知识,即 g[0][0][0/1/2] = 1.
接下来根据 的不同分别考虑转移:
- 若 ,则表示相邻节点无限制.因此,可以从其子节点 的状态转移而来.具体来说,若 是第 个子节点:.注意其中是加号还是乘号.互斥的状态是相加,承接的状态是相乘.
- 若 ,则其相邻节点只能取到 .因此,可以从其子节点 的状态转移而来.具体来说,若 是第 个子节点:.
- 若 ,其相邻节点不能是高功率节点.因此,可以从其子节点 的状态转移而来.具体来说,若 是第 个子节点:.
接下来就完全是树形背包的转移.
最终状态是 .
状态的定义常和问题有关.极简单的状态定义就是根据问题定义状态.例如本题询问有多少种方案数,于是就可以定义 dp 状态为考虑到某个节点(或考虑完某节点的子树)时可行的方案数.
稍复杂的 dp 需要加一些限制条件.如此题,由于一个节点的决策只与其子节点决策和剩余“高功率节点”配额相关,于是需要在状态中增加两维.又由于父节点决策只与子节点决策的范围(与 的大小关系)有关,因此可以坍缩一维大小为 3 的维度,使其成为常数维.需要注意的是,
- 在增加限制时,不得依赖于当前节点的父节点.如,我定义一维状态表示当前节点有没有受到父节点的影响,这是不可行的
- 状态不能重复、不能遗漏.如,我定义一维常数维,其中 0 表示当前节点不受影响的方案数(当前节点可以取 ),1 表示当前节点受影响的方案数(当前状态可以取 ),这两者在 的值域内重叠了.
此外,在转移时,特别是方案数,需要注意运算符号究竟是加号还是乘号.相互排斥的状态需要相加,相互承接的状态需要相乘.
标程
#include <bits/stdc++.h>using namespace std;typedef long long ll;const int N = 3005;const int MOD = 998244353;int n, m, k, x, root;vector<int> e[N];int dp[N][N][3], g[2][N][3], sz[N];void dfs(int u, int fa) { sz[u] = 1; for (int i = 0; i < e[u].size(); i++) { int v = e[u][i]; if (v != fa) { dfs(v, u); sz[u] += sz[v]; } } if (u != root && e[u].size() == 1) { dp[u][0][0] = k - 1; dp[u][1][1] = 1; dp[u][0][2] = m - k; return; } g[0][0][0] = 1; g[0][0][1] = 1; g[0][0][2] = 1; int totsz = 0; int offset = 0; for (int xi = 1; xi <= e[u].size(); xi++) { int v = e[u][xi - 1]; if (v == fa) { offset++; continue; } int i = xi - offset; int now = i & 1, prev = (i + 1) & 1; memset(g[now], 0, sizeof(g[now])); for (int j = 0; j <= totsz; j++) { for (int k = 0; k <= sz[v]; k++) { (g[now][j + k][0] += 1ll * g[prev][j][0] * (0ll + dp[v][k][0] + dp[v][k][1] + dp[v][k][2] % MOD) % MOD) %= MOD; (g[now][j + k][1] += 1ll * g[prev][j][1] * dp[v][k][0] % MOD) %= MOD; (g[now][j + k][2] += 1ll * g[prev][j][2] * (0ll + dp[v][k][0] + dp[v][k][2] % MOD) % MOD) %= MOD; } } totsz += sz[v]; } int maxs = (e[u].size() - offset) & 1; for (int i = 0; i <= totsz; i++) { (dp[u][i][0] = 1ll * g[maxs][i][0] * (k - 1) % MOD) %= MOD; dp[u][i + 1][1] = g[maxs][i][1] % MOD; dp[u][i][2] = 1ll * g[maxs][i][2] * (m - k) % MOD; }}
void solve() { memset(sz, 0, sizeof(sz)); memset(dp, 0, sizeof(dp)); cin >> n >> m >> k >> x; for (int i = 1; i <= n; i++) { e[i].clear(); } root = -1; for (int i = 1; i < n; i++) { int u, v; cin >> u >> v; if (root == -1) { root = u; } e[u].push_back(v); e[v].push_back(u); } dfs(root, 0); ll ans = 0; for (int i = 0; i <= x; i++) { (ans += dp[root][i][0]) %= MOD; (ans += dp[root][i][1]) %= MOD; (ans += dp[root][i][2]) %= MOD; } cout << ans << endl;}
signed main() { int T; cin >> T; while (T--) solve(); return 0;}文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!


