力扣 第 463 场周赛

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^3

n*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;
    }
};

3. 删除可整除和后的最小数组和

给你一个整数数组 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;
    }
};

4. 区间乘法查询后的异或 II

给你一个长度为 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​ 都和 li​modki​ 相等,又因为只要求最后的答案,所以我们可以把这个信息先分类记下来:步长为 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;
    }
};

感谢大家的点赞和关注,你们的支持是我创作的动力!

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值