LCP 05-发 LeetCoin
力扣决定给一个刷题团队发LeetCoin
作为奖励。同时,为了监控给大家发了多少LeetCoin
,力扣有时候也会进行查询。
该刷题团队的管理模式可以用一棵树表示:
- 团队只有一个负责人,编号为1。除了该负责人外,每个人有且仅有一个领导(负责人没有领导);
- 不存在循环管理的情况,如A管理B,B管理C,C管理A。
力扣想进行的操作有以下三种:
- 给团队的一个成员(也可以是负责人)发一定数量的
LeetCoin
; - 给团队的一个成员(也可以是负责人),以及他/她管理的所有人(即他/她的下属、他/她下属的下属,……),发一定数量的
LeetCoin
; - 查询某一个成员(也可以是负责人),以及他/她管理的所有人被发到的
LeetCoin
之和。
输入:
N
表示团队成员的个数(编号为1~N,负责人为1);leadership
是大小为(N - 1) * 2
的二维数组,其中每个元素[a, b]
代表b
是a
的下属;operations
是一个长度为Q
的二维数组,代表以时间排序的操作,格式如下:
1.operations[i][0] = 1
: 代表第一种操作,operations[i][1]
代表成员的编号,operations[i][2]
代表LeetCoin
的数量;
2.operations[i][0] = 2
: 代表第二种操作,operations[i][1]
代表成员的编号,operations[i][2]
代表LeetCoin
的数量;
3.operations[i][0] = 3
: 代表第三种操作,operations[i][1]
代表成员的编号;
输出:
返回一个数组,数组里是每次 查询
的返回值(发LeetCoin
的操作不需要任何返回值)。由于发的LeetCoin
很多,请把每次查询的结果模1e9+7 (1000000007)
。
示例 1:
**输入:** N = 6, leadership = [[1, 2], [1, 6], [2, 3], [2, 5], [1, 4]], operations = [[1, 1, 500], [2, 2, 50], [3, 1], [2, 6, 15], [3, 1]]
**输出:** [650, 665]
**解释:** 团队的管理关系见下图。
第一次查询时,每个成员得到的LeetCoin的数量分别为(按编号顺序):500, 50, 50, 0, 50, 0;
第二次查询时,每个成员得到的LeetCoin的数量分别为(按编号顺序):500, 50, 50, 0, 50, 15.
![](https://assets.leetcode-cn.com/aliyun-lc-
upload/uploads/2019/09/09/coin_example_1.jpg)
限制:
1 <= N <= 50000
1 <= Q <= 50000
operations[i][0] != 3 时,1 <= operations[i][2] <= 5000
关注小爱老师的算法课堂 ,分享高质量算法视频与文字题解
题目分析:深度优先搜索 + 树状数组
首先,本题包括几种操作(为了便于叙述,我们将对象从 LeetCoin
变成分数):
- 给某人加分
- 给一部分人加分
- 查询一部分人的分数之和
操作 1 可以通过两次操作 2 完成:给一部分人(包括指定的人)加分,并将这部分人中,指定的人以外的人减同样的分数。
所以,如果我们将“一部分”这个概念量化到某个数值区间上,本题相当于要求我们实现两种操作:
- 给指定区间 [l, r] 加上指令数值 value
- 查询指令区间 [l, r] 数值之和
所以,我们可以通过深度优先搜索,将题目给定的负责人及其管理人员量化到数轴上一段连续的区间;然后通过树状数组解决区间更新和区间查询的问题。所以在算法细节部分,我们分为两部分。第一部分介绍基于深度优先搜索的量化方法,第二部分介绍基于树状数组的区间处理问题。
算法细节:
DFS
解决量化问题
由于树状数组需要对连续区间进行处理,所以本部分我们要达成的目标是:
- 将所有人的编号映射到 [1, N] 的区间,其中负责人及其团队需要映射到一段连续的区间
- 能通过负责人的编号获取得到其团队的区间 [l, r]。
由于本题按照一棵树的形式,表达一个团队的管理模式,所以可以利用深度优先搜索来完成这件事情。
首先,我们定义一个全局变量 id
,用于将所有成员的编号映射到数值上。
其次,我们定义两个数组 begin[i], end[i]
,记录编号为 i 的成员所管理团队映射到的区间 [l, r]。
下面我们开始 DFS
过程:
- 假设本次
DFS
搜索的是编号为cur
的成员所管理的团队,那么我们首先将begin[cur]
设为id
,表示该成员所管理团队的**起始id
**。 - 对
cur
所管理团队的所有成员进行新一轮的DFS
。 - 当遍历完
cur
管理的团队后,我们将end[cur]
设为当前id
(遍历管理团队时,id
会更新),表示cur
映射到了值id
上。 - 我们将
id
加一,以便继续对还未映射的成员进行映射,并退出本轮搜索。
我们以本题的样例,让大家对本题的深搜过程有更清晰的了解:
我们对 1 进行 DFS,并记录 begin[1] = 1,开始搜索其团队 [2, 6, 4]
我们对 2 进行 DFS,并记录 begin[2] = 1,开始搜索其团队 [3, 5]
我们对 3 进行 DFS,并记录 begin[3] = 1,开始搜索其团队 []
3 搜索完毕,映射到 id = 1,并记录在 end[3] = 1 中,更新 id = 2,退出搜索。
我们对 5 进行 DFS,并记录 begin[5] = 2,开始搜索其团队 []
5 搜索完毕,映射到 id = 2,并记录在 end[5] = 2 中,更新 id = 3,退出搜索。
2 搜索完毕,映射到 id = 3,并记录在 end[2] = 3 中,更新 id = 4,退出搜索。
我们对 6 进行 DFS,并记录 begin[6] = 4,开始搜索其团队 []
6 搜索完毕,映射到 id = 4,并记录在 end[6] = 4 中,更新 id = 5,退出搜索。
我们对 4 进行 DFS,并记录 begin[4] = 5,开始搜索其团队 []
4 搜索完毕,映射到 id = 5,并记录在 end[4] = 5 中,更新 id = 6,退出搜索。
1 搜索完毕,映射到 id = 6,并记录在 end[1] = 6 中,更新 id = 7,退出搜索。
此时整个搜索过程完毕,我们来看一下 6 个人所对应的 begin 和 end:
begin[1] = 1, end[1] = 6,表示编号为 1 的人所管理的团队映射到的区间是 [1, 6],本身映射到 6
begin[2] = 1, end[2] = 3,表示编号为 2 的人所管理的团队映射到的区间是 [1, 3],本身映射到 3
begin[3] = 1, end[3] = 1,表示编号为 3 的人所管理的团队映射到的区间是 [1, 1],本身映射到 1
begin[4] = 5, end[4] = 5,表示编号为 4 的人所管理的团队映射到的区间是 [5, 5],本身映射到 5
begin[5] = 2, end[5] = 2,表示编号为 5 的人所管理的团队映射到的区间是 [2, 2],本身映射到 2
begin[6] = 4, end[6] = 4,表示编号为 6 的人所管理的团队映射到的区间是 [4, 4],本身映射到 4
树状数组解决区间处理问题
如果你还不熟悉树状数组,可以看我的这篇树状数组详解 呀!
在本题的代码实现中,直接利用树状数组的区间处理模板即可。
代码:
1 | using ll = long long; |
1 | M = int(1e9 + 7) |
复杂度分析:
- 时间复杂度:O(qnlgn),其中 q 为查询次数,n 为团队成员个数
- 空间复杂度:O(n)