文章目录
- 周赛370
- [2923. 找到冠军 I](https://leetcode.cn/problems/find-champion-i/)
- 模拟
- [2924. 找到冠军 II](https://leetcode.cn/problems/find-champion-ii/)
- 统计入度
- [2925. 在树上执行操作以后得到的最大分数](https://leetcode.cn/problems/maximum-score-after-applying-operations-on-a-tree/)
- 正难则反 + 树形DP
- [2926. 平衡子序列的最大和](https://leetcode.cn/problems/maximum-balanced-subsequence-sum/)
- 树状数组优化DP
周赛370
2923. 找到冠军 I
简单
一场比赛中共有 n
支队伍,按从 0
到 n - 1
编号。
给你一个下标从 0 开始、大小为 n * n
的二维布尔矩阵 grid
。对于满足 0 <= i, j <= n - 1
且 i != j
的所有 i, j
:如果 grid[i][j] == 1
,那么 i
队比 j
队 强 ;否则,j
队比 i
队 强 。
在这场比赛中,如果不存在某支强于 a
队的队伍,则认为 a
队将会是 冠军 。
返回这场比赛中将会成为冠军的队伍。
示例 1:
输入:grid = [[0,1],[0,0]]
输出:0
解释:比赛中有两支队伍。
grid[0][1] == 1 表示 0 队比 1 队强。所以 0 队是冠军。
示例 2:
输入:grid = [[0,0,1],[1,0,1],[0,0,0]]
输出:1
解释:比赛中有三支队伍。
grid[1][0] == 1 表示 1 队比 0 队强。
grid[1][2] == 1 表示 1 队比 2 队强。
所以 1 队是冠军。
提示:
n == grid.length
n == grid[i].length
2 <= n <= 100
grid[i][j]
的值为0
或1
- 对于满足
i != j
的所有i, j
,grid[i][j] != grid[j][i]
均成立 - 生成的输入满足:如果
a
队比b
队强,b
队比c
队强,那么a
队比c
队强
模拟
class Solution {
/**
本质是某一行 i 除了 g[i][i] 其他都为 1
*/
public int findChampion(int[][] grid) {
int n = grid.length;
for(int i = 0; i < n; i++){
boolean winner = true;
for(int j = 0; j < n; j++){
if(i == j) continue;
if(grid[i][j] == 0){
winner = false;
break;
}
}
if(winner) return i;
}
return -1;
}
}
2924. 找到冠军 II
中等
一场比赛中共有 n
支队伍,按从 0
到 n - 1
编号。每支队伍也是 有向无环图(DAG) 上的一个节点。
给你一个整数 n
和一个下标从 0 开始、长度为 m
的二维整数数组 edges
表示这个有向无环图,其中 edges[i] = [ui, vi]
表示图中存在一条从 ui
队到 vi
队的有向边。
从 a
队到 b
队的有向边意味着 a
队比 b
队 强 ,也就是 b
队比 a
队 弱 。
在这场比赛中,如果不存在某支强于 a
队的队伍,则认为 a
队将会是 冠军 。
如果这场比赛存在 唯一 一个冠军,则返回将会成为冠军的队伍。否则,返回 -1
。
注意
- 环 是形如
a1, a2, ..., an, an+1
的一个序列,且满足:节点a1
与节点an+1
是同一个节点;节点a1, a2, ..., an
互不相同;对于范围[1, n]
中的每个i
,均存在一条从节点ai
到节点ai+1
的有向边。 - 有向无环图 是不存在任何环的有向图。
示例 1:
输入:n = 3, edges = [[0,1],[1,2]]
输出:0
解释:1 队比 0 队弱。2 队比 1 队弱。所以冠军是 0 队。
示例 2:
输入:n = 4, edges = [[0,2],[1,3],[1,2]]
输出:-1
解释:2 队比 0 队和 1 队弱。3 队比 1 队弱。但是 1 队和 0 队之间不存在强弱对比。所以答案是 -1 。
提示:
1 <= n <= 100
m == edges.length
0 <= m <= n * (n - 1) / 2
edges[i].length == 2
0 <= edge[i][j] <= n - 1
edges[i][0] != edges[i][1]
- 生成的输入满足:如果
a
队比b
队强,就不存在b
队比a
队强 - 生成的输入满足:如果
a
队比b
队强,b
队比c
队强,那么a
队比c
队强
统计入度
class Solution {
public int findChampion(int n, int[][] edges) {
List<Integer>[] g = new ArrayList[n];
Arrays.setAll(g, e -> new ArrayList<>());
int[] deg = new int[n];
for(int[] e : edges){
int x = e[0], y = e[1];
g[x].add(y);
deg[y] += 1;
}
int res = -1, cnt = 0;
for(int i = 0; i < n; i++){
if(deg[i] == 0){
cnt += 1;
res = i;
}
}
return cnt == 1 ? res : -1;
}
}
2925. 在树上执行操作以后得到的最大分数
中等
有一棵 n
个节点的无向树,节点编号为 0
到 n - 1
,根节点编号为 0
。给你一个长度为 n - 1
的二维整数数组 edges
表示这棵树,其中 edges[i] = [ai, bi]
表示树中节点 ai
和 bi
有一条边。
同时给你一个长度为 n
下标从 0 开始的整数数组 values
,其中 values[i]
表示第 i
个节点的值。
一开始你的分数为 0
,每次操作中,你将执行:
- 选择节点
i
。 - 将
values[i]
加入你的分数。 - 将
values[i]
变为0
。
如果从根节点出发,到任意叶子节点经过的路径上的节点值之和都不等于 0 ,那么我们称这棵树是 健康的 。
你可以对这棵树执行任意次操作,但要求执行完所有操作以后树是 健康的 ,请你返回你可以获得的 最大分数 。
示例 1:
输入:edges = [[0,1],[0,2],[0,3],[2,4],[4,5]], values = [5,2,5,2,1,1]
输出:11
解释:我们可以选择节点 1 ,2 ,3 ,4 和 5 。根节点的值是非 0 的。所以从根出发到任意叶子节点路径上节点值之和都不为 0 。所以树是健康的。你的得分之和为 values[1] + values[2] + values[3] + values[4] + values[5] = 11 。
11 是你对树执行任意次操作以后可以获得的最大得分之和。
示例 2:
输入:edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]], values = [20,10,9,7,4,3,5]
输出:40
解释:我们选择节点 0 ,2 ,3 和 4 。
- 从 0 到 4 的节点值之和为 10 。
- 从 0 到 3 的节点值之和为 10 。
- 从 0 到 5 的节点值之和为 3 。
- 从 0 到 6 的节点值之和为 5 。
所以树是健康的。你的得分之和为 values[0] + values[2] + values[3] + values[4] = 40 。
40 是你对树执行任意次操作以后可以获得的最大得分之和。
提示:
2 <= n <= 2 * 104
edges.length == n - 1
edges[i].length == 2
0 <= ai, bi < n
values.length == n
1 <= values[i] <= 109
- 输入保证
edges
构成一棵合法的树。
正难则反 + 树形DP
https://leetcode.cn/problems/maximum-score-after-applying-operations-on-a-tree/solutions/2513101/shu-xing-dpxuan-huo-bu-xuan-pythonjavacg-7aj6/
class Solution {
/**
正难则反,先把所有数都选上,加入到答案中,然后考虑不选哪些点权
对于一棵以 x 的根的子树,如果这棵树是健康的,损失的最小分数是多少
选或不选
不选 损失根节点的情况下,损失的最小分数 = values[x]
选 不损失根节点(根节点加到答案种) = sum(dfs(y))
两种情况去最小值
*/
public long maximumScoreAfterOperations(int[][] edges, int[] values) {
List<Integer>[] g = new ArrayList[values.length];
Arrays.setAll(g, e -> new ArrayList<>());
g[0].add(-1); // 避免误把根节点当成叶子
for(int[] e : edges){
int x = e[0], y = e[1];
g[x].add(y);
g[y].add(x);
}
// 先把所有分数加入答案
long ans = 0;
for(int v : values) ans += v;
return ans - dfs(0, -1, g, values);
}
// dfs(x) 计算以 x 为根的子树是健康时,失去的最小分数
public long dfs(int x, int fa, List<Integer>[] g, int[] values){
if(g[x].size() == 1){ // x 是叶子
return values[x];
}
long loss = 0; // 第二种情况
for(int y : g[x]){
if(y != fa){
loss += dfs(y, x, g, values);
}
}
return Math.min(values[x], loss); // 两种情况取最小值
}
}
2926. 平衡子序列的最大和
困难
给你一个下标从 0 开始的整数数组 nums
。
nums
一个长度为 k
的 子序列 指的是选出 k
个 下标 i0 < i1 < ... < ik-1
,如果这个子序列满足以下条件,我们说它是 平衡的 :
- 对于范围
[1, k - 1]
内的所有j
,nums[ij] - nums[ij-1] >= ij - ij-1
都成立。
nums
长度为 1
的 子序列 是平衡的。
请你返回一个整数,表示 nums
平衡 子序列里面的 最大元素和 。
一个数组的 子序列 指的是从原数组中删除一些元素(也可能一个元素也不删除)后,剩余元素保持相对顺序得到的 非空 新数组。
示例 1:
输入:nums = [3,3,5,6]
输出:14
解释:这个例子中,选择子序列 [3,5,6] ,下标为 0 ,2 和 3 的元素被选中。
nums[2] - nums[0] >= 2 - 0 。
nums[3] - nums[2] >= 3 - 2 。
所以,这是一个平衡子序列,且它的和是所有平衡子序列里最大的。
包含下标 1 ,2 和 3 的子序列也是一个平衡的子序列。
最大平衡子序列和为 14 。
示例 2:
输入:nums = [5,-1,-3,8]
输出:13
解释:这个例子中,选择子序列 [5,8] ,下标为 0 和 3 的元素被选中。
nums[3] - nums[0] >= 3 - 0 。
所以,这是一个平衡子序列,且它的和是所有平衡子序列里最大的。
最大平衡子序列和为 13 。
示例 3:
输入:nums = [-2,-1]
输出:-1
解释:这个例子中,选择子序列 [-1] 。
这是一个平衡子序列,而且它的和是 nums 所有平衡子序列里最大的。
提示:
1 <= nums.length <= 105
-109 <= nums[i] <= 109
树状数组优化DP
https://leetcode.cn/problems/maximum-balanced-subsequence-sum/solutions/2513121/shu-zhuang-shu-zu-you-hua-dp-by-endlessc-3zf4/
class Solution {
/**
思考过程
nums[ij] - nums[ij-1] >= ij - ij-1
nums[i] - nums[j] >= i - j
==> nums[i] - i >= nums[j] - j
定义 b[i] = nums[i] - i
b[i] >= b[j]
==> 从 b 中选一个子序列,满足这个子序列是一个非递减的序列
求对应的元素和的最大值
a 3 3 5 6
b 3 2 4 3
定义 f[i] = 以下标 i 结尾的子序列,对应的 nums 的元素和的最大值
转移 f[i] = f[j] + nums[i], j < i && b[j] <= b[i]
使用值域树状数组优化,用来维护前缀最大值,设下标为x=b[i],维护的值为max(f[x], f[x-1], f[x-2]..)
实现时,需要先把nums[i] - i离散化
*/
public long maxBalancedSubsequenceSum(int[] nums) {
int n = nums.length;
int[] b = new int[n];
for(int i = 0; i < n; i++){
b[i] = nums[i] - i;
}
Arrays.sort(b);
BIT t = new BIT(b.length+1);
for(int i = 0; i < n; i++){
// j 为 nums[i]-i 离散化后的值(从 1 开始)
int j = Arrays.binarySearch(b, nums[i] - i) + 1;
long f = Math.max(t.preMax(j), 0) + nums[i];
t.update(j, f);
}
return t.preMax(b.length);
}
}
// 树状数组模板(维护前缀最大值)
class BIT {
private long[] tree;
public BIT(int n) {
tree = new long[n];
Arrays.fill(tree, Long.MIN_VALUE);
}
public void update(int i, long val) {
while (i < tree.length) {
tree[i] = Math.max(tree[i], val);
i += i & -i;
}
}
public long preMax(int i) {
long res = Long.MIN_VALUE;
while (i > 0) {
res = Math.max(res, tree[i]);
i &= i - 1;
}
return res;
}
}