在传球游戏中最大化函数值

题目

2836. 在传球游戏中最大化函数值


给你一个长度为 n 下标从 0 开始的整数数组 receiver 和一个整数 k 。

总共有 n 名玩家,玩家 编号 互不相同,且为 [0, n - 1] 中的整数。这些玩家玩一个传球游戏,receiver[i] 表示编号为 i 的玩家会传球给编号为 receiver[i] 的玩家。玩家可以传球给自己,也就是说 receiver[i] 可能等于 i 。

你需要从 n 名玩家中选择一名玩家作为游戏开始时唯一手中有球的玩家,球会被传 恰好 k 次。

如果选择编号为 x 的玩家作为开始玩家,定义函数 f(x) 表示从编号为 x 的玩家开始,k 次传球内所有接触过球玩家的编号之  ,如果有玩家多次触球,则 累加多次 。换句话说, f(x) = x + receiver[x] + receiver[receiver[x]] + ... + receiver^(k)[x] 。

你的任务时选择开始玩家 x ,目的是 最大化 f(x) 。

请你返回函数的 最大值 。

注意:receiver 可能含有重复元素。

示例 1:

传递次数

传球者编号

接球者编号

x + 所有接球者编号

 

 

 

2

1

2

1

3

2

1

0

3

3

0

2

5

4

2

1

6

1
2
3
4
5
6
输入:receiver = [2,0,1], k = 4
输出:6
解释:上表展示了从编号为 x = 2 开始的游戏过程。
从表中可知,f(2) 等于 6 。
6 是能得到最大的函数值。
所以输出为 6 。

示例 2:

传递次数

传球者编号

接球者编号

x + 所有接球者编号

 

 

 

4

1

4

3

7

2

3

2

9

3

2

1

10

1
2
3
4
5
6
输入:receiver = [1,1,1,2,3], k = 3
输出:10
解释:上表展示了从编号为 x = 4 开始的游戏过程。
从表中可知,f(4) 等于 10 。
10 是能得到最大的函数值。
所以输出为 10 。

提示:

  • 1 <= receiver.length == n <= 10^5
  • 0 <= receiver[i] <= n - 1
  • 1 <= k <= 10^10

题解

方法一:

思路

注意到图中存在环,且有可能不止一个。对于传球k次,在进入环后,可以$O(1)$计算环绕次数。如果预处理出了所有环的长度,以及各个环内所有元素和。非环内元素到环的距离以及到环路径上的元素和。做到这些后可以$O(1)$计算每个点作为球的起点的f值。

但是这样码量巨大,短时间内可能调不出来。

可以用$O(nlogn)$的倍增算法替代,简洁巧妙。

倍增法还是运用了动态规划的思想。

我们预处理出每个节点作为传球起点时,传球0,2,4,8,等2的幂次次数后球所处的位置。以及这些经过的点的编号和。

更准确的定义,$f_{i,j}$是从j位置移动$2^i$所到达的位置,$s_{i,j}$为从j位置移动$2^i$次中通经过点的编号和(不含移动$2^i$次后的点编号)。

显然可以初始化$f_{0,j} = receiver[j], s_{0,j} = j$

状态转移,对于已知i的第j个祖先,如何知道i的第2j个祖先?答案就是i的第j个祖先的第j个祖先。

所以有$f_{i,j} = f_{i-1, f_{i-1, j}}$。

同理可以合并两段路径和$s_{i,j} = s_{i-1,j}+s_{i-1, s_{i-1, j}}$

对于k的值最大为$10^{10}$,由于$2^{34}=17179869184>10^{10}$最多只需要处理到$f_{34, n}$

而我们已经预处理出每个点可以传2的幂次步数后的位置以及路径上的编号和。

由于k可以分解为不超过34个不同的2的幂次之和,(k的二进制位不超过34位),所以每个点计算的传球所花时间是$O(logn)$的。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public:
using ll = long long;
long long getMaxFunctionValue(vector<int>& rec, long long k) {
int n = rec.size();
vector<vector<ll>> f(35, vector<ll>(n)), s(35, vector<ll>(n));
for (int i=0; i<n; i++) {
f[0][i] = rec[i];
s[1][i] = i;
}
for (int i=1; i<35; i++) {
for (int j=0; j<n; j++) {
f[i][j] = f[i-1][f[i-1][j]];
s[i][j] = s[i-1][j]+s[i-1][f[i-1][j]];
}
}
ll ans = 0;
for (int i=0; i<n; i++) {
ll c = i, tans = 0;
for (int j=0; j<35; j++) {
if (k>>j&1) {
tans += s[j][c];
c = f[j][c];
}
}
ans = max(ans, tans+c); // s包含起点不含终点
}
return ans;
}
};