网易首页 > 网易号 > 正文 申请入驻

2026-08-31:统计有根树中不相邻子集的数目。用go语言,给定一棵包含 n 个节点的有根树,节点编号为 0 到 n-1,其中 0 号节点是根。每个

0
分享至

2026-08-31:统计有根树中不相邻子集的数目。用go语言,给定一棵包含 n 个节点的有根树,节点编号为 0 到 n-1,其中 0 号节点是根。每个节点的父节点由一个数组 parent 给出,根节点的父节点为 -1,其他节点的父节点编号一定小于该节点本身。同时,每个节点上还有一个整数值,存放在数组 nums 中,另外给定一个整数 k。

我们需要统计所有满足以下两个条件的非空节点集合的数量:

  1. 1. 集合中所有节点的数值之和能够被 k 整除;

  2. 2. 集合中不能同时包含任意一个节点和它的直接父节点,也就是说选出的节点在树中不能有相邻的父子关系。

最终结果需要对 1000000007 取模后输出。

n == parent.length == nums.length

1 <= n <= 1000

parent[0] == -1

对于所有的 1 <= i < n:

0 <= parent[i] < i

1 <= nums[i] <= 1000000000

1 <= k <= 100

parent 表示一棵有效的有根树。

输入: parent = [-1,0,0,0], nums = [2,1,2,1], k = 3。

输出: 2。

解释:


在这里插入图片描述

有效的子集有:

{1, 2}:节点 1 和 2 都是节点 0 的子节点,且彼此不直接相连。它们的值之和为 1 + 2 = 3 ,可以被 3 整除。

{2, 3}:节点 2 和 3 也不相邻。它们的值之和为 2 + 1 = 3 ,可以被 3 整除。

没有其他子集同时满足两个条件。因此,答案是 2 。

题目来自力扣3939。

合并子节点的详细过程

假设我们已经递归计算了某个子节点 y 的状态fy0fy1,现在要将 y 合并到当前节点 x 的f0f1中。

1. 更新f0(不选 x)

此时,由于 x 未被选中,子节点 y可以被选,也可以不被选。因此,从 y 子树中选取的合法集合有两种情况:

  • • y 不被选,对应fy0

  • • y 被选,对应fy1

所以,子节点 y 对整体余数的贡献总和为v[i] = fy0[i] + fy1[i](每种余数 i 的方案数相加)。

现在,当前已有的不选 x 的方案数为f0(这是已经处理完之前若干个兄弟子树的累计结果)。当我们把 y 的贡献合并进来时,相当于将两个“余数分布”进行卷积:新余数 = (i + j) % k,其中 i 来自子节点 y 贡献的余数,j 来自之前已处理的子树贡献的余数。新的方案数累加到nf0[(i+j)%k]中。

最后,用nf0替换原有的f0

2. 更新f1(选 x)

此时,x 已被选中,那么其直接子节点 y 绝对不能选(因为 y 是 x 的子节点,二者相邻)。因此,y 子树只能提供 y 不被选时的方案,即fy0

类似地,将fy0与当前已有的f1(已经处理完的兄弟子树)进行卷积,得到新的nf1,并替换原有的f1

递归过程说明

  • • 整棵树通过parent数组构建邻接表,根节点为 0。

  • • 从根节点开始执行深度优先搜索(DFS),递归地处理每个节点。

  • • 每个节点在处理完所有子节点后,返回自己的f0f1给父节点。

  • • 父节点在得到子节点的返回结果后,按照上述规则合并。

最终答案的计算

当根节点 0 的递归返回后,我们得到了整棵树的f0f1(分别对应不选根和选根两种全局状态)。

  • • 合法的非空集合总数 = (不选根时,余数为 0 的方案数) + (选根时,余数为 0 的方案数)。

  • • 但这两个方案数中都包含了空集(因为初始的f0[0]=1就代表空集,而选根时不可能包含空集,所以只有f0里有空集),所以最后需要减去空集这一种方案

即答案 =(f0[0] + f1[0] - 1) mod MOD,最后取模得到正整数结果。

时间复杂度

  • • 每个节点在合并其每个子节点时,都需要两层循环分别遍历余数 0 到 k-1,因此每次合并的时间开销为O(k^2)

  • • 树中总共有 n 个节点,每条边对应一次合并操作,边的数量为n-1

  • • 因此总时间复杂度为O(n · k^2)

  • • 在本题限制下,n ≤ 1000k ≤ 100,故最多约1000 × 10000 = 1e7次基本运算,完全可行。

额外空间复杂度
  • • 递归深度最坏情况下为O(n)(例如链状树)。

  • • 在递归栈的每一层,每个节点会保存若干个长度为 k 的数组(f0f1,以及合并时的临时数组),因此递归路径上同时存在的数组总大小约为O(k)乘以递归深度,即O(n · k)

  • • 同时,合并过程中产生的临时数组会在函数返回后自动释放,不会累积。

  • • 因此,额外空间复杂度为O(n · k),在给定范围内(n=1000, k=100)约为1e5级别,内存充足。

示例验证(以题目输入为例)
  • parent = [-1,0,0,0],根为 0,子节点为 1、2、3。

  • nums = [2,1,2,1]k=3

  • • 叶子节点 1、2、3 分别递归返回。

  • • 根 0 合并子节点后,最终统计余数为 0 的方案数(减去空集)得到答案 2,即{1,2}{2,3}两种有效子集,与题意相符。

总结

该算法利用树形 DP 巧妙地处理了“不相邻”和“和整除 k”两个约束,通过分情况(选/不选当前节点)以及卷积合并子节点的方式,在O(n·k²)时间内完成了统计。代码实现清晰,适合本题的数据规模。

Go完整代码如下:

package main

import (
"fmt"
)

func countValidSubsets(parent []int, nums []int, k int) int {
const mod = 1_000_000_007
n := len(parent)
g := make([][]int, n)
for i := 1; i < n; i++ {
p := parent[i]
g[p] = append(g[p], i)
}

var dfs func(int) ([]int, []int)
dfs = func(x int) ([]int, []int) {
f0 := make([]int, k) // f0[i] 表示不选 x 时,子树 x 的子集点权和模 k 为 i 的方案数
f1 := make([]int, k) // f1[i] 表示选 x 时,子树 x 的子集点权和模 k 为 i 的方案数
f0[0] = 1
f1[nums[x]%k] = 1

for _, y := range g[x] {
fy0, fy1 := dfs(y)

// 不选 x,那么 y 可选可不选
nf0 := make([]int, k)
for i := range k { // 枚举从子树 y 中选出的点权和模 k 为 i
v := fy0[i] + fy1[i]
if v == 0 { // 优化
continue
}
for j, w := range f0 { // 枚举从之前的子树中选出的点权和模 k 为 j
s := (i + j) % k
nf0[s] = (nf0[s] + v*w) % mod
}
}

// 选 x,那么 y 不能选
nf1 := make([]int, k)
for i, v := range fy0 { // 枚举从子树 y 中选出的点权和模 k 为 i
if v == 0 { // 优化
continue
}
for j, w := range f1 { // 枚举从 x 以及之前的子树中选出的点权和模 k 为 j
s := (i + j) % k
nf1[s] = (nf1[s] + v*w) % mod
}
}
f0, f1 = nf0, nf1
}

return f0, f1
}

f0, f1 := dfs(0)
// 恰好被 k 整除即模 k 为 0,注意减去空集的方案数 1
return (f0[0] + f1[0] - 1 + mod) % mod
}

func main() {
parent := []int{-1, 0, 0, 0}
nums := []int{2, 1, 2, 1}
k := 3
result := countValidSubsets(parent, nums, k)
fmt.Println(result)
}

Python完整代码如下:

# -*-coding:utf-8-*-

import sys

def countValidSubsets(parent, nums, k):
MOD = 10**9 + 7
n = len(parent)
# 构建邻接表
g = [[] for _ in range(n)]
for i in range(1, n):
p = parent[i]
g[p].append(i)

sys.setrecursionlimit(max(1000000, n * 2 + 10))

def dfs(x):
# f0: 不选当前节点 x 时的方案数(按模 k 分类)
# f1: 选当前节点 x 时的方案数(按模 k 分类)
f0 = [0] * k
f1 = [0] * k
f0[0] = 1 # 空集
f1[nums[x] % k] = 1 # 只含 x 自身的子集

for y in g[x]:
fy0, fy1 = dfs(y) # 递归处理子节点

# ----- 情况1:不选 x,则子节点 y 可选可不选 -----
nf0 = [0] * k
for i in range(k):
v = (fy0[i] + fy1[i]) % MOD
if v == 0:
continue
for j in range(k):
if f0[j] == 0:
continue
s = (i + j) % k
nf0[s] = (nf0[s] + v * f0[j]) % MOD

# ----- 情况2:选 x,则子节点 y 不能选 -----
nf1 = [0] * k
for i in range(k):
v = fy0[i] # 子节点只能取不选 y 的方案
if v == 0:
continue
for j in range(k):
if f1[j] == 0:
continue
s = (i + j) % k
nf1[s] = (nf1[s] + v * f1[j]) % MOD

f0, f1 = nf0, nf1

return f0, f1

f0, f1 = dfs(0)
# 根节点结果 = 不选根 + 选根,再减去空集(1 种)
return (f0[0] + f1[0] - 1) % MOD

# 示例测试
if __name__ == "__main__":
parent = [-1, 0, 0, 0]
nums = [2, 1, 2, 1]
k = 3
print(countValidSubsets(parent, nums, k))

C++完整代码如下:

  

using namespace std;

const long long MOD = 1000000007LL;

pair , vector > dfs( int x, const vector int >>& g, const vector< int >& nums, int k) {
vector f0(k, 0 ), f1(k, 0 );
f0[ 0 ] = 1 ; // 不选 x 的空集
f1[nums[x] % k] = 1 ; // 选 x 的集合(仅包含 x)

for ( int y : g[x]) {
auto [fy0, fy1] = dfs(y, g, nums, k);

// 不选 x,则子节点 y 可选可不选
vector nf0(k, 0 );
for ( int i = 0 ; i < k; ++i) {
long long v = (fy0[i] + fy1[i]) % MOD;
if (v == 0 ) continue ;
for ( int j = 0 ; j < k; ++j) {
if (f0[j] == 0 ) continue ;
int s = (i + j) % k;
nf0[s] = (nf0[s] + v * f0[j]) % MOD;
}
}

// 选 x,则子节点 y 不能选
vector nf1(k, 0 );
for ( int i = 0 ; i < k; ++i) {
long long v = fy0[i]; // 只能选 y 中不选 y 的方案
if (v == 0 ) continue ;
for ( int j = 0 ; j < k; ++j) {
if (f1[j] == 0 ) continue ;
int s = (i + j) % k;
nf1[s] = (nf1[s] + v * f1[j]) % MOD;
}
}

f0 = move(nf0);
f1 = move(nf1);
}

return {f0, f1};
}

int countValidSubsets( const vector< int >& parent, const vector< int >& nums, int k) {
int n = parent.size();
vector int >> g(n);
for ( int i = 1 ; i < n; ++i) {
int p = parent[i];
g[p].push_back(i);
}

auto [f0, f1] = dfs( 0 , g, nums, k);
long long ans = (f0[ 0 ] + f1[ 0 ] - 1 ) % MOD; // 减去空集
if (ans < 0 ) ans += MOD;
return ( int )ans;
}

int main() {
vector< int > parent = { -1 , 0 , 0 , 0 };
vector< int > nums = { 2 , 1 , 2 , 1 };
int k = 3 ;
int result = countValidSubsets(parent, nums, k);
cout << result << endl;
return 0 ;
}

我们相信人工智能为普通人提供了一种“增强工具”,并致力于分享全方位的AI知识。在这里,您可以找到最新的AI科普文章、工具评测、提升效率的秘籍以及行业洞察。 欢迎关注“福大大架构师每日一题”,发消息可获得面试资料,让AI助力您的未来发展。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
陈东任芜湖市代市长

陈东任芜湖市代市长

网易安徽
2026-08-31 17:54:32
1.25亿新援训练量仅队友1/6,穆里尼奥为何每场60分钟后换上他?

1.25亿新援训练量仅队友1/6,穆里尼奥为何每场60分钟后换上他?

宝哥精彩赛事
2026-09-01 00:45:41
连续6个一字涨停板!股民:撞大运了!

连续6个一字涨停板!股民:撞大运了!

数据挖掘分析
2026-08-31 15:17:39
尼泊尔铁血女总统:叫板印度力挺中国,见印军压境,直接出兵对峙

尼泊尔铁血女总统:叫板印度力挺中国,见印军压境,直接出兵对峙

共工之锚
2026-08-31 00:28:37
白月光的杀伤力有多大?网友:白月光就是没得到的人,得到了也就那样

白月光的杀伤力有多大?网友:白月光就是没得到的人,得到了也就那样

带你感受人间冷暖
2026-08-20 00:18:45
留韩女生殒命真相追踪!国内有男友,为何还踏入纠缠者的凌晨私宅?

留韩女生殒命真相追踪!国内有男友,为何还踏入纠缠者的凌晨私宅?

火山詩话
2026-08-27 15:18:01
“女儿竟然要在这种环境住3年”,家长实拍宿舍环境,心都碎了

“女儿竟然要在这种环境住3年”,家长实拍宿舍环境,心都碎了

泽泽先生
2026-08-11 13:03:55
央视主持刘璐:忙碌35年退休,不到一年成精神病患者,为何会这样

央视主持刘璐:忙碌35年退休,不到一年成精神病患者,为何会这样

青杉依旧啊啊
2026-08-29 18:36:22
外媒就中国和尼泊尔边境吉隆-热索瓦口岸泥石流灾害成因发表不当言论 外交部驳斥

外媒就中国和尼泊尔边境吉隆-热索瓦口岸泥石流灾害成因发表不当言论 外交部驳斥

新京报
2026-08-31 15:40:27
胜黎巴嫩王俊杰激动落泪!采访如释重负,提杨瀚森亲承默契!

胜黎巴嫩王俊杰激动落泪!采访如释重负,提杨瀚森亲承默契!

篮球资讯达人
2026-08-31 22:22:50
“每一个都生无可恋、毫无朝气”,军训学生面相火了,家长:精气神呢

“每一个都生无可恋、毫无朝气”,军训学生面相火了,家长:精气神呢

番外行
2026-08-29 10:25:00
央妈“摸排”结果:全国能一次性拿出50万的家庭,数量超乎想象!

央妈“摸排”结果:全国能一次性拿出50万的家庭,数量超乎想象!

平说财经
2026-08-29 07:39:18
A股:大家做好准备,又有消息来临,明天会迎来新一轮大反弹吗?

A股:大家做好准备,又有消息来临,明天会迎来新一轮大反弹吗?

财经大拿
2026-08-31 14:15:20
郑钦文哭泣!解说:她庆祝就像夺得大满贯 全世界都知道她有多强

郑钦文哭泣!解说:她庆祝就像夺得大满贯 全世界都知道她有多强

念洲
2026-08-29 08:25:55
张继科没叫景甜“妈妈”!伐木累玩剧组夫妻被抓5次!

张继科没叫景甜“妈妈”!伐木累玩剧组夫妻被抓5次!

八卦疯叔
2026-08-30 11:20:08
文班7中6轰18+8+3帽!肌肉对比照刷屏,帕克:马刺夺冠只是时间问题

文班7中6轰18+8+3帽!肌肉对比照刷屏,帕克:马刺夺冠只是时间问题

锅子篮球
2026-08-31 09:42:38
农历七月财运持续飙升的3个生肖,对朋友仗义,日子从来不穷!

农历七月财运持续飙升的3个生肖,对朋友仗义,日子从来不穷!

毅谈生肖
2026-08-26 11:38:03
胡锡进怒斥孙宇晨:对前女友下毒手,就是地狱级恶毒

胡锡进怒斥孙宇晨:对前女友下毒手,就是地狱级恶毒

自愈小日子
2026-09-01 02:19:55
罗马诺:巴萨告知小蜘蛛经纪人明年签他;阿森纳仍在密切关注

罗马诺:巴萨告知小蜘蛛经纪人明年签他;阿森纳仍在密切关注

懂球帝
2026-08-31 17:00:08
21岁,全场最佳!曼联1.55亿买的中场,还不如自家青训一场球

21岁,全场最佳!曼联1.55亿买的中场,还不如自家青训一场球

曹老师评球
2026-08-31 08:20:46
2026-09-01 03:19:00
moonfdd incentive-icons
moonfdd
福大大架构师每日一题
1428文章数 80关注度
往期回顾 全部

科技要闻

起售不足18万!特斯拉在港澳推廉价Model 3

头条要闻

60米高空直击吉隆口岸 这就是泥石流曾吞噬的高度

头条要闻

60米高空直击吉隆口岸 这就是泥石流曾吞噬的高度

体育要闻

中国女婿用一份满分答卷,刺痛中国女排

娱乐要闻

女歌手陈粒疑被男子骚扰,本人回应

财经要闻

沃什美联储百日新政剧变:没有给答案

汽车要闻

A0级最长续航 极狐贝塔T1 550km版上市 7.68万起

态度原创

房产
手机
游戏
公开课
军事航空

房产要闻

稳住了!三亚最新房价,刺破3.5万元/m²!

手机要闻

苹果折叠屏iPhone正在测试手写笔:乔布斯当年最嫌弃的配件回归

GTA6新截图曝光!双主角犯罪|亲密大赏

公开课

李玫瑾:为什么性格比能力更重要?

军事要闻

时隔30年 东南亚将迎来第二艘航母

无障碍浏览 进入关怀版