2818-操作使得分最大

Raphael Liu Lv10

给你一个长度为 n 的正整数数组 nums 和一个整数 k

一开始,你的分数为 1 。你可以进行以下操作至多 k 次,目标是使你的分数最大:

  • 选择一个之前没有选过的 非空 子数组 nums[l, ..., r]
  • nums[l, ..., r] 里面选择一个 质数分数 最高的元素 x 。如果多个元素质数分数相同且最高,选择下标最小的一个。
  • 将你的分数乘以 x

nums[l, ..., r] 表示 nums 中起始下标为 l ,结束下标为 r 的子数组,两个端点都包含。

一个整数的 质数分数 等于 x 不同质因子的数目。比方说, 300 的质数分数为 3 ,因为 `300 = 2 * 2 * 3 * 5

  • 5` 。

请你返回进行至多 k 次操作后,可以得到的 最大分数

由于答案可能很大,请你将结果对 109 + 7 取余后返回。

示例 1:

**输入:** nums = [8,3,9,3,8], k = 2
**输出:** 81
**解释:** 进行以下操作可以得到分数 81 :
- 选择子数组 nums[2, ..., 2] 。nums[2] 是子数组中唯一的元素。所以我们将分数乘以 nums[2] ,分数变为 1 * 9 = 9 。
- 选择子数组 nums[2, ..., 3] 。nums[2] 和 nums[3] 质数分数都为 1 ,但是 nums[2] 下标更小。所以我们将分数乘以 nums[2] ,分数变为 9 * 9 = 81 。
81 是可以得到的最高得分。

示例 2:

**输入:** nums = [19,12,14,6,10,18], k = 3
**输出:** 4788
**解释:** 进行以下操作可以得到分数 4788 :
- 选择子数组 nums[0, ..., 0] 。nums[0] 是子数组中唯一的元素。所以我们将分数乘以 nums[0] ,分数变为 1 * 19 = 19 。
- 选择子数组 nums[5, ..., 5] 。nums[5] 是子数组中唯一的元素。所以我们将分数乘以 nums[5] ,分数变为 19 * 18 = 342 。
- 选择子数组 nums[2, ..., 3] 。nums[2] 和 nums[3] 质数分数都为 2,但是 nums[2] 下标更小。所以我们将分数乘以 nums[2] ,分数变为  342 * 14 = 4788 。
4788 是可以得到的最高的分。

提示:

  • 1 <= nums.length == n <= 105
  • 1 <= nums[i] <= 105
  • 1 <= k <= min(n * (n + 1) / 2, 109)

请看 视频讲解 第四题。

本文接着 贡献法+单调栈 继续讲,请先阅读这篇题解,因为核心逻辑是一样的。

贪心地说,先考虑 nums 中最大的元素 x,我们需要知道:有多少个子数组可以取 x 作为质数分数最高的元素。

我们可以先把 [1,10^5] 中的每个数的质数分数(不同质因子的数目)预处理出来,记作数组 omega。

然后用单调栈求出每个 i 左侧质数分数【大于等于】omega}[\textit{nums}[i]] 的最近的数的下标 left}[i](不存在则为 -1),以及右侧质数分数【大于】omega}[\textit{nums}[i]] 的最近的数的下标 right}[i](不存在则为 n)。

请注意,我们不能在 i 左侧包含质数分数和 omega}[\textit{nums}[i]] 一样的数,否则不满足题目「如果多个元素质数分数相同且最高,选择下标最小的一个」的要求。所以对于左侧用【大于等于】。

那么子数组的左端点可以在 (\textit{left}[i],i] 内,子数组的右端点可以在 [i,\textit{right}[i]) 内。

根据 乘法原理 ,一共有

\textit{tot} = (i-\textit{left}[i])\cdot (\textit{right}[i]-i)

个子数组以 nums}[i] 作为这个子数组的质数分数。

设 k’=\min(k, \textit{tot}),则 nums}[i] 对答案的贡献为

\textit{nums}[i] ^ {k’}

上式可以用快速幂计算,具体请看 50. Pow(x, n)

根据上式,按照 nums 从大到小的顺序计算贡献,一边计算一边更新剩余操作次数 k。

[sol-Python3]
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
MOD = 10 ** 9 + 7
MX = 10 ** 5 + 1
omega = [0] * MX
for i in range(2, MX): # 预处理 omega
if omega[i] == 0: # i 是质数
for j in range(i, MX, i):
omega[j] += 1 # i 是 j 的一个质因子

class Solution:
def maximumScore(self, nums: List[int], k: int) -> int:
n = len(nums)
left = [-1] * n # 质数分数 >= omega[nums[i]] 的左侧最近元素下标
right = [n] * n # 质数分数 > omega[nums[i]] 的右侧最近元素下标
st = []
for i, v in enumerate(nums):
while st and omega[nums[st[-1]]] < omega[v]:
right[st.pop()] = i
if st: left[i] = st[-1]
st.append(i)

ans = 1
for i, v, l, r in sorted(zip(range(n), nums, left, right), key=lambda z: -z[1]):
tot = (i - l) * (r - i)
if tot >= k:
ans = ans * pow(v, k, MOD) % MOD
break
ans = ans * pow(v, tot, MOD) % MOD
k -= tot # 更新剩余操作次数
return ans
[sol-Java]
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Solution {
private static final long MOD = (long) 1e9 + 7;
private static final int MX = (int) 1e5 + 1;
private static final int[] omega = new int[MX];

static {
for (int i = 2; i < MX; i++)
if (omega[i] == 0) // i 是质数
for (int j = i; j < MX; j += i)
omega[j]++; // i 是 j 的一个质因子
}

public int maximumScore(List<Integer> nums, int k) {
var a = nums.stream().mapToInt(i -> i).toArray();
int n = a.length;
var left = new int[n]; // 质数分数 >= omega[nums[i]] 的左侧最近元素下标
var right = new int[n];// 质数分数 > omega[nums[i]] 的右侧最近元素下标
Arrays.fill(right, n);
var st = new ArrayDeque<Integer>();
st.push(-1); // 方便赋值 left
for (int i = 0; i < n; i++) {
while (st.size() > 1 && omega[a[st.peek()]] < omega[a[i]])
right[st.pop()] = i;
left[i] = st.peek();
st.push(i);
}

var ids = new Integer[n];
for (int i = 0; i < n; i++) ids[i] = i;
Arrays.sort(ids, (i, j) -> a[j] - a[i]);

long ans = 1;
for (int i : ids) {
long tot = (long) (i - left[i]) * (right[i] - i);
if (tot >= k) {
ans = ans * pow(a[i], k) % MOD;
break;
}
ans = ans * pow(a[i], (int) tot) % MOD;
k -= tot; // 更新剩余操作次数
}
return (int) ans;
}

private long pow(long x, int n) {
var res = 1L;
for (; n > 0; n /= 2) {
if (n % 2 > 0) res = res * x % MOD;
x = x * x % MOD;
}
return res;
}
}
[sol-C++]
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
const int MX = 1e5 + 1;
int omega[MX];
int init = []() {
for (int i = 2; i < MX; i++)
if (omega[i] == 0) // i 是质数
for (int j = i; j < MX; j += i)
omega[j]++; // i 是 j 的一个质因子
return 0;
}();

class Solution {
const long long MOD = 1e9 + 7;

long long pow(long long x, int n) {
long long res = 1;
for (; n; n /= 2) {
if (n % 2) res = res * x % MOD;
x = x * x % MOD;
}
return res;
}

public:
int maximumScore(vector<int> &nums, int k) {
int n = nums.size();
vector<int> left(n, -1); // 质数分数 >= omega[nums[i]] 的左侧最近元素下标
vector<int> right(n, n); // 质数分数 > omega[nums[i]] 的右侧最近元素下标
stack<int> st;
for (int i = 0; i < n; i++) {
while (!st.empty() && omega[nums[st.top()]] < omega[nums[i]]) {
right[st.top()] = i;
st.pop();
}
if (!st.empty()) left[i] = st.top();
st.push(i);
}

vector<int> id(n);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(), [&](const int i, const int j) {
return nums[i] > nums[j];
});

long long ans = 1;
for (int i: id) {
long long tot = (long long) (i - left[i]) * (right[i] - i);
if (tot >= k) {
ans = ans * pow(nums[i], k) % MOD;
break;
}
ans = ans * pow(nums[i], tot) % MOD;
k -= tot; // 更新剩余操作次数
}
return ans;
}
};
[sol-Go]
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
const mod int = 1e9 + 7

// 预处理 omega
const mx int = 1e5
var omega [mx + 1]int8
func init() {
for i := 2; i <= mx; i++ {
if omega[i] == 0 { // i 是质数
for j := i; j <= mx; j += i {
omega[j]++ // i 是 j 的一个质因子
}
}
}
}

func maximumScore(nums []int, k int) int {
n := len(nums)
left := make([]int, n) // 质数分数 >= omega[nums[i]] 的左侧最近元素下标
right := make([]int, n) // 质数分数 > omega[nums[i]] 的右侧最近元素下标
for i := range right {
right[i] = n
}
st := []int{-1}
for i, v := range nums {
for len(st) > 1 && omega[nums[st[len(st)-1]]] < omega[v] {
right[st[len(st)-1]] = i
st = st[:len(st)-1]
}
left[i] = st[len(st)-1]
st = append(st, i)
}

id := make([]int, n)
for i := range id {
id[i] = i
}
sort.Slice(id, func(i, j int) bool { return nums[id[i]] > nums[id[j]] })

ans := 1
for _, i := range id {
tot := (i - left[i]) * (right[i] - i)
if tot >= k {
ans = ans * pow(nums[i], k) % mod
break
}
ans = ans * pow(nums[i], tot) % mod
k -= tot // 更新剩余操作次数
}
return ans
}

func pow(x, n int) int {
res := 1
for ; n > 0; n /= 2 {
if n%2 > 0 {
res = res * x % mod
}
x = x * x % mod
}
return res
}

复杂度分析

  • 时间复杂度:\mathcal{O}(n\log n),其中 n 为 nums 的长度。这里忽略预处理 omega 的时间和空间。
  • 空间复杂度:\mathcal{O}(n)。

相似题目:单调栈+贡献法

 Comments
On this page
2818-操作使得分最大