1. 按策略买卖股票的最佳时机
给你两个整数数组 prices 和 strategy,其中:
prices[i] 表示第 i 天某股票的价格。
strategy[i] 表示第 i 天的交易策略,其中:
-1 表示买入一单位股票。
0 表示持有股票。
1 表示卖出一单位股票。
同时给你一个 偶数 整数 k,你可以对 strategy 进行 最多一次 修改。一次修改包括:选择 strategy 中恰好 k 个 连续 元素。
将前 k / 2 个元素设为 0(持有)。
将后 k / 2 个元素设为 1(卖出)。
利润 定义为所有天数中 strategy[i] * prices[i] 的 总和 。返回你可以获得的 最大 可能利润。
注意: 没有预算或股票持有数量的限制,因此所有买入和卖出操作均可行,无需考虑过去的操作。
解题思路:容易想到滑窗的思路, 通过计算增量, 结果就是原来的乘积+增量, 先计算第一个窗口, 对于数组strategy, 前k/2个数变成0, 增量为-strategy[I]*prices[i], 后k/2变成1, 增量为prices[i]*1-strategy[I]*prices[i], 向右滑动的过程中, 下标为i-k/2的数会从1变成0, 若变化后增量全是负值, 此时保持原值更优, 也就是增量和0取最大值
using ll = long long;
class Solution {
public:
long long maxProfit(vector<int>& prices, vector<int>& strategy, int k) {
ll tot = 0, sum = 0;
for (int i = 0; i < k / 2; i++) {
int p = prices[i], s = strategy[i];
tot += p * s;
sum -= p * s;
}
for (int i = k / 2; i < k; i++) {
int p = prices[i], s = strategy[i];
tot += p * s;
sum += p * (1 - s);
}
ll max_sum = max(sum, 0LL);
for (int i = k; i < prices.size(); i++) {
int p = prices[i], s = strategy[i];
tot += p * s;
sum += p * (1 - s) - prices[i - k / 2] +
prices[i - k] * strategy[i - k];
max_sum = max(max_sum, sum);
}
return max_sum + tot;
}
};
// [i,i+k/2-1]/[i-k,i-k/2-1] - 前k/2
// [i-k/2,i-1]
2. 区间乘法查询后的异或 I
给你一个长度为 n 的整数数组 nums 和一个大小为 q 的二维整数数组 queries,其中 queries[i] = [li, ri, ki, vi]。
对于每个查询,按以下步骤执行操作:
设定 idx = li。
当 idx <= ri 时:
更新:nums[idx] = (nums[idx] * vi) % (109 + 7)
将 idx += ki。
在处理完所有查询后,返回数组 nums 中所有元素的 按位异或 结果。
解题思路:
0 <= li <= ri < n
1 <= q == queries.length <= 10^3n*q的时间复杂度, 直接按题意模拟即可
const int MOD = 1e9 + 7;
using ll = long long;
class Solution {
public:
int xorAfterQueries(vector<int>& nums, vector<vector<int>>& queries) {
vector<ll> nums_;
for (int i = 0; i < nums.size(); i++)
nums_.push_back(nums[i]);
for (int i = 0; i < queries.size(); i++) {
int x = queries[i][0], y = queries[i][1], z = queries[i][2],
e = queries[i][3];
for (int j = x; j <= y; j += z) {
nums_[j] = (nums_[j] * e) % MOD;
}
}
ll ans = 0;
for (int i = 0; i < nums_.size(); i++) {
ans = ans ^ nums_[i];
}
return ans;
}
};
给你一个整数数组 nums 和一个整数 k。
你可以 多次 选择 连续 子数组 nums,其元素和可以被 k 整除,并将其删除;每次删除后,剩余元素会填补空缺。
返回在执行任意次数此类删除操作后,nums 的最小可能 和。
解题思路:DP,每次删除的元素和都是k的倍数, 对于数组的最后一个元素nums[n-1], 删除的话, 寻找当前待删子数组的左端点i, 下标i...n-1是k的倍数, 删除子数组 [i,n−1] 后,问题变成前缀 [0,i−1] 的最小和, 可能有多个满足条件的左端点i, 题中求删除后nums的最小可能和, 因此删除和最大的子数组和, 保留剩下最小的数组和。状态转移时, 不删就是f+x, 删的话, 变为剩余前缀的最小值
using ll = long long;
class Solution {
public:
long long minArraySum(vector<int>& nums, int k) {
vector<ll> a(k, LLONG_MAX);
a[0] = 0;
ll f = 0;
int s = 0;
for (int i = 0; i < nums.size(); i++) {
s = (s + nums[i]) % k;
f = min(f + nums[i], a[s]);
a[s] = f;
}
return f;
}
};
给你一个长度为 n 的整数数组 nums 和一个大小为 q 的二维整数数组 queries,其中 queries[i] = [li, ri, ki, vi]。
对于每个查询,需要按以下步骤依次执行操作:设定 idx = li。
当 idx <= ri 时:
更新:nums[idx] = (nums[idx] * vi) % (109 + 7)。
将 idx += ki。
在处理完所有查询后,返回数组 nums 中所有元素的 按位异或 结果。
解题思路:大数据范围 - 不会写 - 转载佬的
如果 ki>n,我们直接暴力计算,因为下标每次增加 n,最多加 n 次就到 n 了。维护这种操作的复杂度是 O(qn)。
如果 ki≤n,注意到本次操作被修改的下标 modki 都和 limodki 相等,又因为只要求最后的答案,所以我们可以把这个信息先分类记下来:步长为 ki,且下标 modki 为特定值,且位于区间 [li,ri] 里的所有下标都要乘以 vi。步长只有 n 种,每种步长的 mod 也只有 n 种,因此我们只有 O(n) 类信息要记录。
怎么把我们记录的信息还原成答案呢?我们枚举每类信息,这样问题变为:每次给一个区间乘以 vi,问每个数最后的值。这就是 leetcode 1109. 航班预定统计,对操作区间排序,使用差分 + 扫描线的思想即可离线处理。1109 题里,加法的逆运算是减法,本题里模意义下乘法的逆运算是求逆元。
还原答案的复杂度是多少呢?注意到相同步长不同余数的下标是不会重复的,因此每种步长会恰好把所有下标枚举一遍,因此我们会枚举 O(nn) 次下标,再加上对操作的排序,因此复杂度为 O(qlogq+nn)。
最后考虑求逆元的复杂度,整体复杂度为 O(q(logq+logM)+nn),其中 M=109+7 是模数。
class Solution {
public:
int xorAfterQueries(vector<int>& nums, vector<vector<int>>& queries) {
int n = nums.size(), B = sqrt(n);
// 求乘法逆元
const int MOD = 1e9 + 7;
auto inv = [&](long long a) {
long long b = MOD - 2, y = 1;
for (; b; b >>= 1) {
if (b & 1) y = (y * a) % MOD;
a = a * a % MOD;
}
return y;
};
long long A[n];
for (int i = 0; i < n; i++) A[i] = nums[i];
typedef pair<int, long long> pil;
vector<pil> vec[B + 1][B + 1];
for (auto &qry : queries) {
int l = qry[0], r = qry[1], K = qry[2], v = qry[3];
if (K <= B) {
// 步长不超过根号,先把操作记下来
// 差分思想:记录操作开始的位置以及原运算,再记录操作结束的位置以及逆运算
vec[K][l % K].push_back({l, v});
vec[K][l % K].push_back({r + 1, inv(v)});
} else {
// 步长超过根号,暴力处理
for (int i = l; i <= r; i += K) A[i] = A[i] * v % MOD;
}
}
// 枚举每一类操作
for (int k = 1; k <= B; k++) for (int m = 0; m < k; m++) {
// 把操作按下标从左到右排序
sort(vec[k][m].begin(), vec[k][m].end());
// 扫描线维护当前乘积
long long now = 1;
// 枚举这一类里的所有下标
for (int i = m, j = 0; i < n; i += k) {
// 用扫描线进行需要的操作
while (j < vec[k][m].size() && vec[k][m][j].first <= i) {
now = now * vec[k][m][j].second % MOD;
j++;
}
A[i] = A[i] * now % MOD;
}
}
long long ans = 0;
for (int i = 0; i < n; i++) ans ^= A[i];
return ans;
}
};
感谢大家的点赞和关注,你们的支持是我创作的动力!
202

被折叠的 条评论
为什么被折叠?



