面条老师捡到一颗黑白相间的树,这颗树有 n n 个节点,其中 k k 个节点是黑色的。 面条老师觉得,现在的树配色实在是杂乱无章,于是面条老师决定使用他祖传的剪刀来对这颗树进行修剪,面条老师可以选择一个边的集合 e 1 , e 2 . . . , e k ( ∀ i ∈ [ 1.. k ] , j ∈ [ 1.. k ] e 1 ,e 2 ...,e k (∀i∈[1..k],j∈[1..k], 如果 i ≠ j i =j, 那么就有 e i ≠ e j ) e i =e j ),然后将这些边全部用祖传剪刀剪掉。可以预料到的是,树会分裂成 k + 1 k+1 个小树。面条老师希望这 k + 1 k+1 个小树都恰好有一个黑色节点。 面条老师在准备开始剪之前突然想到一个问题:有多少种不同的边的集合可以让修剪后的 k + 1 k+1 个小树都恰好有一个黑色节点呢? 面条老师修剪树是一把好手,可是在思考问题上却遇到了很大的困难。你作为面条老师的好朋友,你能告诉他这个不同的边的集合的数量是多少么? 输入描述 第一行是一个
这是一个树形动态规划问题。我们需要计算有多少种方式删除一些边,使得每个连通块恰好有一个黑色节点。 ## 问题分析 给定一棵树,有 $n$ 个节点,其中 $k$ 个是黑色节点。我们需要删除一些边,使得剩下的每个连通块恰好有一个黑色节点。 关键观察: - 如果某个子树包含 0 个黑色节点,那么它必须与父节点所在的连通块合并。 - 如果某个子树包含 1 个黑色节点,那么它可以独立成为一个连通块(即切断它与父节点的边)。 - 如果某个子树包含超过 1 个黑色节点,那么它不能独立,必须与父节点连通。 ## 动态规划状态 定义 `dp[u][0]` 和 `dp[u][1]`: - `dp[u][0]`:以节点 u 为根的子树,不包含黑色节点时,满足条件的方案数。 - `dp[u][1]`:以节点 u 为根的子树,恰好包含一个黑色节点时,满足条件的方案数。 转移规则: 1. 如果节点 u 是黑色: - `dp[u][1]` 初始为 1(只包含 u 自己)。 - 对于每个子节点 v: - 如果保留边 (u, v),那么 v 的子树必须贡献 0 个黑色节点(即 `dp[v][0]`)。 - 如果切断边 (u, v),那么 v 的子树必须恰好有 1 个黑色节点(即 `dp[v][1]`)。 - 所以 `dp[u][1] = product_{v in children(u)} (dp[v][0] + dp[v][1])`。 2. 如果节点 u 是白色: - `dp[u][0]` 初始为 1(只包含 u 自己,没有黑色节点)。 - `dp[u][1]` 初始为 0。 - 对于每个子节点 v: - 如果保留边 (u, v),那么 v 的子树贡献可以合并到 u 的子树中。 - 如果切断边 (u, v),那么 v 的子树必须恰好有 1 个黑色节点。 - 具体转移: - 新的 `dp[u][0] = dp[u][0] * (dp[v][0] + dp[v][1])`。 - 新的 `dp[u][1] = dp[u][1] * (dp[v][0] + dp[v][1]) + dp[u][0] * dp[v][1]`。 最终答案: 根节点的 `dp[root][1]` 就是答案,因为整棵树必须恰好有 1 个黑色节点(实际上整棵树有 k 个黑色节点,但通过切断边,每个连通块恰好一个黑色节点,所以根所在的连通块也恰好一个黑色节点)。 ## 代码实现 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; const int MOD = 1e9 + 7; vector<vector<int>> adj; vector<int> color; vector<vector<long long>> dp; void dfs(int u, int parent) { if (color[u] == 1) { // 黑色节点 dp[u][1] = 1; for (int v : adj[u]) { if (v == parent) continue; dfs(v, u); dp[u][1] = dp[u][1] * ((dp[v][0] + dp[v][1]) % MOD) % MOD; } } else { // 白色节点 dp[u][0] = 1; for (int v : adj[u]) { if (v == parent) continue; dfs(v, u); long long sum_v = (dp[v][0] + dp[v][1]) % MOD; long long new_dp0 = dp[u][0] * sum_v % MOD; long long new_dp1 = (dp[u][1] * sum_v % MOD + dp[u][0] * dp[v][1] % MOD) % MOD; dp[u][0] = new_dp0; dp[u][1] = new_dp1; } } } int main() { int n; cin >> n; // 读取树结构 vector<int> parent(n-1); for (int i = 0; i < n-1; i++) { cin >> parent[i]; } // 构建邻接表 adj.resize(n); for (int i = 0; i < n-1; i++) { int u = i + 1; int v = parent[i]; adj[u].push_back(v); adj[v].push_back(u); } // 读取颜色 color.resize(n); for (int i = 0; i < n; i++) { cin >> color[i]; } // 初始化DP数组 dp.assign(n, vector<long long>(2, 0)); // 从节点0开始DFS dfs(0, -1); // 输出结果 cout << dp[0][1] % MOD << endl; return 0; } ``` ## 算法复杂度 - 时间复杂度:O(n),每个节点访问一次。 - 空间复杂度:O(n),用于存储树和DP状态。 ## 示例解释 对于输入: ``` 3 0 0 1 0 1 ``` 表示: - 3个节点 - 边:(1,0), (2,0) - 颜色:节点0黑色,节点1白色,节点2黑色 可能的方案: 1. 不删除任何边:连通块包含2个黑色节点,不符合条件。 2. 删除边(1,0):得到两个连通块:{0,2}(2个黑色节点)和{1}(0个黑色节点),不符合条件。 3. 删除边(2,0):得到两个连通块:{0,1}(1个黑色节点)和{2}(1个黑色节点),符合条件。 4. 删除两条边:得到三个连通块:{0}(1个黑色节点)、{1}(0个黑色节点)、{2}(1个黑色节点),不符合条件。 只有方案3符合条件,所以答案是1。 这个算法可以正确处理所有情况,包括多个黑色节点的复杂树结构。 [2026-01-20 19:14:45 | AI问答 | 1421点数解答]
- [字节豆包] 题目描述 石头剪刀布是常见的猜拳游戏:石头胜剪刀,剪刀胜布,布胜石头。如果两个人出拳 一样,则不分胜负。在《生活大爆炸》第二季第 8 集中出现了一种石头剪刀布的升级 版游戏。 升级版游戏在传统的石头剪刀布游戏的基础上,增加了两个新手势: 斯波克:《星际迷航》主角之一。 蜥蜴人:《星际迷航》中的反面角色。 这五种手势的胜负关系如表一所示,表中列出的是甲对乙的游戏结果。 现在,小 A 和小 B 尝试玩这种升级版的猜拳游戏。已知他们的出拳都是有周期性规 律的,但周期长度不一定相等。例如:如果小 A 以 石头-布-石头-剪刀-蜥蜴人-斯波 克 长度为 6 的周期出拳,那么他的出拳序列就是 石头-布-石头-剪刀-蜥蜴人-斯波克 -石头-布-石头-剪刀-蜥蜴人-斯波克-...,而如果小 B 以 剪刀-石头-布-斯波克-蜥 蜴人 长度为 5 的周期出拳,那么他出拳的序列就是 剪刀-石头-布-斯波克-蜥蜴人-剪 刀-石头-布-斯波克-蜥蜴人-...。 已知小 A 和小 B 一共进行 N 次猜拳。每一次赢的人得 1 分,输的得 0 分;平局两 人都得 0 分。现请你统计 N 次猜拳结束(656点数解答 | 2025-11-26 18:54:37)42
- [DeepSeek] - ItemId: 12720 #道具id A级赛车 雷诺 ItemNum: 1 #数量 ObtainTime: 1 #时间 AvailPeriod: -1 #0显示数量 -1显示永久 - ItemId: 127758 #道具id 宠物 冰凤 ItemNum: 1 #数量 ObtainTime: 1 #时间 AvailPeriod: -1 #0显示数量 -1显示永久 - ItemId: 21980 #道具id 效率宝珠LV4 ItemNum: 100 #数量 ObtainTime: 1 #时间 AvailPeriod: 0 #0显示数量 -1显示永久 - ItemId: 21986 #道具id 重生宝珠LV4 ItemNum: 100 #数量 ObtainTime: 1 #时间 AvailPeriod: 0 #0显示数量 -1显示永久 这种文本文件如何用易语言读入并显示到超级列表框内 (571点数解答 | 2025-08-23 20:54:40)194
- [DeepSeek] - ItemId: 12720 #道具id A级赛车 雷诺 ItemNum: 1 #数量 ObtainTime: 1 #时间 AvailPeriod: -1 #0显示数量 -1显示永久 - ItemId: 127758 #道具id 宠物 冰凤 ItemNum: 1 #数量 ObtainTime: 1 #时间 AvailPeriod: -1 #0显示数量 -1显示永久 - ItemId: 21980 #道具id 效率宝珠LV4 ItemNum: 100 #数量 ObtainTime: 1 #时间 AvailPeriod: 0 #0显示数量 -1显示永久 - ItemId: 21986 #道具id 重生宝珠LV4 ItemNum: 100 #数量 ObtainTime: 1 #时间 AvailPeriod: 0 #0显示数量 -1显示永久 这种文本文件如何用易语言读入并显示到超级列表框内,并且可以增加新的一样的文本(1317点数解答 | 2025-08-23 20:58:40)198
- [字节豆包] pandas读取文件,文件某一列分组,条件为列数据字段中包含“一级”为一组,没有“一级”的为一组,将pandas读取到的文件按地市映射表分为各地市文件,再将这个文件当作邮件附件,邮件正文为某地市,有“一级”多少,没有“一级”多少,语言方向:Python,系统环境:Windows(459点数解答 | 2024-12-25 01:17:06)244
- [字节豆包] 用C++给定一个整数 N N,判断其正负。如果 N > 0 N>0,输出 p o s i t i v e positive; 如果 N = 0 N=0,输出 z e r o zero; 如果 N < 0 N<0,输出 n e g a t i v e negative。 输入 一个整数 N ( − 10 9 ≤ N ≤ 10 9 ) N(−10 9 ≤N≤10 9 )。 输出 如果 N > 0 N>0, 输出 p o s i t i v e positive; 如果 N = 0 N=0, 输出 z e r o zero; 如果 N < 0 N<0, 输出 n e g a t i v e negative。 回答篇幅:越详细越好(610点数解答 | 2026-01-24 22:28:14)35
- [字节豆包] 用C++给定一个整数 N N,判断其正负。如果 N > 0 N>0,输出 p o s i t i v e positive; 如果 N = 0 N=0,输出 z e r o zero; 如果 N < 0 N<0,输出 n e g a t i v e negative。 输入 一个整数 N ( − 10 9 ≤ N ≤ 10 9 ) N(−10 9 ≤N≤10 9 )。 输出 如果 N > 0 N>0, 输出 p o s i t i v e positive; 如果 N = 0 N=0, 输出 z e r o zero; 如果 N < 0 N<0, 输出 n e g a t i v e negative。(150点数解答 | 2026-01-24 22:29:16)39
- [DeepSeek] 通常,人们习惯将所有 位二进制串按照字典序排列,例如所有 2 位二进制串按字典序从小到大排列为:00,01,10,11。 格雷码(Gray Code)是一种特殊的 位二进制串排列法,它要求相邻的两个二进制串间**恰好**有一位**不同**,特别地,第一个串与最后一个串也算作相邻。 所有 2 位二进制串按格雷码排列的一个例子为:00,01,11,10。 位格雷码不止一种,下面给出其中一种格雷码的生成算法: 1. 1 位格雷码由两个 1 位二进制串组成,顺序为:0,1。 2. 位格雷码的前 个二进制串,可以由依此算法生成的 位格雷码(总共 个 位二进制串)按**顺序**排列,再在每个串前加一个前缀 0 构成。 3. 位格雷码的后 个二进制串,可以由依此算法生成的 位格雷码(总共 个 位二进制串)按**逆序**排列,再在每个串前加一个前缀 1 构成。 综上, 位格雷码,由 位格雷码的 个二进制串按顺序排列再加前缀 0,和按逆序排列再加前缀 1 构成,共 个二进制串。另外,对于 位格雷码中的 个 二进制串,我们按上(443点数解答 | 2026-01-05 12:21:49)26
- [DeepSeek] 题目描述 通常,人们习惯将所有<math xmlns="http://www.w3.org/1998/Math/MathML"> <mi>n</mi> </math> 位二进制串按照字典序排列,例如所有 2 位二进制串按字典序从小到大排列为:00,01,10,11。 格雷码(Gray Code)是一种特殊的 <math xmlns="http://www.w3.org/1998/Math/MathML"> <mi>n</mi> </math> 位二进制串排列法,它要求相邻的两个二进制串间**恰好**有一位**不同**,特别地,第一个串与最后一个串也算作相邻。 所有 2 位二进制串按格雷码排列的一个例子为:00,01,11,10。 <math xmlns="http://www.w3.org/1998/Math/MathML"> <mi>n</mi> </math>位格雷码不止一种,下面给出其中一种格雷码的生成算法: 1. 1 位格雷码由两个 1 位二进制串组成,顺序为:0,1。 2.<math xmlns="http://www.w3.org/1998/Mat(726点数解答 | 2026-01-13 12:31:37)31
- [字节豆包] 用python写出来 给定一个整数 N N ,判断其正负。如果 N > 0 N>0 ,输出positive;如果 N = 0 N=0 ,输出zero;如果 N < 0 N<0 ,输出negative。 输入格式 一个整数 N N( − 10 9 ≤ N ≤ 10 9 −10 9 ≤N≤10 9 )。 输出格式 如果 N > 0 N>0, 输出positive; 如果 N = 0 N=0, 输出zero; 如果 N < 0 N<0, 输出negative。(45点数解答 | 2026-01-29 17:03:54)13
- [DeepSeek] Hanks 博士是 **(Bio-Tech,生物技术)领域的知名专家。现在,他正在为一个细胞实验做准备工作:培养细胞样本。 Hanks 博士手里现在有 𝑁 种细胞,编号从 1 ∼ 𝑁 ,一个第 𝑖 种细胞经过 1 秒钟可以分裂为 𝑆 𝑖 个同种细胞( 𝑆 𝑖 为正整数)。现在他需要选取某种细胞的一个放进培养皿,让其自由分裂,进行培养。一段时间以后,再把培养皿中的所有细胞平均分入 𝑀 个试管,形成 𝑀 份样本,用于实验。Hanks 博士的试管数 𝑀 很大,普通的计算机的基本数据类型无法存储这样大的 𝑀 值,但万幸的是, 𝑀 总可以表示为 𝑚 1 的 𝑚 2 次方,即 𝑀 = 𝑚 1 𝑚 2 ,其中 𝑚 1 , 𝑚 2 均为基本数据类型可以存储的正整数。 注意,整个实验过程中不允许分割单个细胞,比如某个时刻若培养皿中有 4 个细胞,Hanks 博士可以把它们分入 2 个试管,每试管内 2 个,然后开始实验。但如果培养皿中有 5 个细胞,博士就无法将它们均分入 2 个试管。此时,(1657点数解答 | 2026-01-11 17:55:17)20
- [字节豆包] 题目描述 扶苏来到了一个迷宫,这个迷宫是一个 n 行 m 列的数字矩阵,第 i 行第 j 列写有 一个数字 ai,j。保证 1≤ai,j≤4。 扶苏会在这个迷宫的某一个位置。假设她当前在迷宫的第 i 行第 j 列: 如果 ai,j=1,则她会向上移动一行,即 i 减小 1。 如果 ai,j=2,则她会向下移动一行,即 i 增大 1。 如果 ai,j=3,则她会向左移动一列,即 j 减小 1。 如果 ai,j=4,则她会向右移动一列,即 j 增大 1。 初始时和每次移动到另一个格子后,她都会按上述的规则继续进行新的移动。 当她移动出迷宫,即到达第 0 行、第 0 列、第 n+1 行、第 m+1 列时,称她离开了 迷宫。 现在,你要回答 q 次询问,每次给定一个扶苏的初时位置,你要回答她在多少次移动 后会离开迷宫,或报告她永远不会离开迷宫。 输入格式 第一行是三个整数,表示迷宫的行数 n、列数 m 和询问数 q。 接下来 n 行,每行 m 个整数,表示矩阵 a。 接下来 q 行,每行两个整数 xi,yi,表示一次查询初时在第 xi行yi列时她离开迷宫需 要的步数。(819点数解答 | 2025-11-26 19:27:35)52
- [字节豆包] 题目描述 在甜甜圈王国中,每颗甜甜圈都有一个甜度值 S 来衡量其甜蜜程度。根据甜度的不同,甜甜圈被评定为不同的等级,具体规则如下: 如果 S 在 0 到 25 之间(包含 0 和 25 ),输出 "普通甜甜圈"; 如果 S 在 26 到 50 之间(包含 26 和 50 ),输出 "美味甜甜圈"; 如果 S 在 51 到 75 之间(包含 51 和 75 ),输出 "极品甜甜圈"; 如果 S 在 76 到 99 之间(包含 76 和 99 ),输出 "绝世甜甜圈"; 如果 S 等于 100 ,输出 "传说甜甜圈"。 请根据给定的甜度值 S,输出对应的甜甜圈等级名称。 输入格式 一行一个整数 S,表示甜甜圈的甜度值。(243点数解答 | 2025-12-06 18:35:50)60