【质数分解+单调栈+快速幂】LeetCode 2818. 操作使得分最大
题目
https://leetcode.cn/problems/apply-operations-to-maximize-score/description/
题解
可以使用 \(O(nlogn)\) 时间复杂度预处理出每个数的质因数个数(不重复计算),记录在数组 \(score\),其中 \(score[x]\) 代表元素 \(x\) 的不重复质因数个数。求质因数个数的过程的时间复杂度是个调和级数,调和级数的复杂度证明可见以下博客:
https://www.cnblogs.com/RomanLin/p/19244514
对于子数组 \(nums[l...r]\) 选择的元素 \(x\) 是在范围 \(l \leq i \leq r\) 种质数分数最大者中下标位置最小者,不妨假设下标位置为 \(i\),那么 \(x\) 就可以贡献 \((i - l + 1) * (r - i + 1)\) 次。
那么,要做的就是计算出数组 \(nums\) 中的每个元素能贡献的次数,随后元素从大到小贡献给答案,直至 \(k\) 次贡献次数被使用完。
对于元素 \(x\),使用单调栈维护出其左侧最相邻的质数分数大于等于 \(x\) 质数分数的位置,再使用单调栈维护出其右侧最相邻的质数分数大于 \(x\) 质数分数的位置。
由于 \(k\) 的数据范围为 \(1 \leq k \leq min(\frac{n \times (n + 1)}{2}, 10^9)\),因此贡献到答案中时,需要使用快速幂进行计算。
参考代码
#define MOD 1000000007
#define N 100001
int idx[N];
int monoStk[N];
int top;
int score[N];// 维护每个元素的质数分数
auto init = []() {
for (int i = 2; i < N; ++ i) {
if (!score[i]) {// 若未被访问过,即不存在除本身和 1 以外的因子,则是质数
for (int j = i; j < N; j += i) {
++ score[j];// 质数 i 是元素 j 的质因子
}
}
}
return 0;
}();
long long qpow(long long base, int exponent) {
long long res = 1LL;
while (exponent) {
if (exponent & 1) {
res *= base;
res %= MOD;
}
base *= base;
base %= MOD;
exponent >>= 1;
}
return res;
}
class Solution {
public:
int maximumScore(vector<int>& nums, int k) {
long long ans = 1LL;
int n = nums.size();
top = -1;
iota(idx, idx + n, 0);
vector<int> left(n), right(n);
// 单调栈比较的是质数分数
// 先维护出每个元素右侧最相邻的质数分数大于自身质数分数的位置
for (int i = 0; i < n; ++ i) {
while (top != -1 && score[nums[monoStk[top]]] < score[nums[i]]) {
right[monoStk[top]] = i;
-- top;
}
monoStk[++ top] = i;
}
while (top != -1) {
right[monoStk[top]] = n;
-- top;
}
// 再维护出每个元素左侧最相邻的质数分数大于自身质数分数的位置
for (int i = n - 1; i >= 0; -- i) {
while (top != -1 && score[nums[monoStk[top]]] <= score[nums[i]]) {
left[monoStk[top]] = i;
-- top;
}
monoStk[++ top] = i;
}
while (top != -1) {
left[monoStk[top]] = -1;
-- top;
}
// 首先根据元素大小排序,其次根据可贡献次数排序
sort(idx, idx + n, [&](int i, int j) {
if (nums[i] != nums[j]) return nums[i] > nums[j];
return right[i] - left[i] > right[j] - left[j];
});
// 计算分数使用的是元素值
for (int i = 0; i < n && k > 0; ++ i) {
long long mi = min(1LL * k, (long long)(right[idx[i]] - idx[i]) * (idx[i] - left[idx[i]]));// 计算 nums[idx[i]] 最多可以贡献的次数
k -= mi;// 减少可贡献次数
ans = ans * qpow(nums[idx[i]], mi) % MOD;// 对答案贡献 mi 次 nums[idx[i]]
}
return ans;
}
};
浙公网安备 33010602011771号