双周赛 95 题解

plus2047 于 2023-01-07 发布

本周周赛中规中矩,但我感觉第三题公式推导比第四题还难。。。

1.

image image

「翻译」题目,把题目文本用代码写出来就行了。

class Solution:
    def categorizeBox(self, length: int, width: int, height: int, mass: int) -> str:
        bulky = max(length, width, height) >= 10 ** 4 or length * width * height >= 10 ** 9
        heavy = mass >= 100
        if bulky and heavy:
            return "Both"
        if not bulky and not heavy:
            return "Neither"
        if bulky and not heavy:
            return "Bulky"
        return "Heavy"

2.

image

滑动窗口题目。可以维护一个滑动窗口,并维护窗口中等于 value 的数字个数。

class DataStream {
    queue<int> nums;
    int eq, value, k;
public:
    DataStream(int value, int k): eq(0), value(value), k(k) {}
    
    bool consec(int num) {
        nums.push(num);
        // 若 num == values 则更新 eq
        eq += num == value;

        // 窗口元素多于 k 则 pop 一个
        if(nums.size() > k) {
            eq -= nums.front() == value;
            nums.pop();
        }
        return eq == k;
    }
};

3.

image

这个题目的推导有些意思。推导后的结果是,待求值就等于所有输入数字的 xor. 推导过程写在注释中。

我感觉这个题目的推导是要比第四题难的,xor 跟 , & 可交换得好好证明一会儿。
## xor(i, j, k) 代表关于所有 i, j, k 下表组合进行 xor
# xor(i, j, k) (x[i] | x[j]) & x[k]
# => xor(i, j) xor(k) (x[i] | x[j]) & x[k]
## 可以证明, (x & y1) ^ (x & y2) = x & (y1 ^ y2)
## 这个证明可以按位进行,在一个 bit 位上穷举即可
# => xor(i) xor(j) (x[i] | x[j]) & (xor(k) x[k])
## 类似可以证明,(x | y1) ^ (x | y2)= x | (y1 ^ y2)
# => xor(i) (x[i] | xor(j) x[j]) & (xor(k) x[k])
# => (xor(i) x[i]) | (xor(j) x[j]) & (xor(k) x[k])
# => xor(i) x[i]
class Solution:
    def xorBeauty(self, nums: List[int]) -> int:
        return functools.reduce(lambda x, y: x ^ y, nums)

4.

image image

这是一道二分答案+滑动窗口+贪心的题目。

使用二分答案方法的前提是,验证一个答案要比求解容易得多。本题中,验证能否到达某个供电站数目确实比较容易。我们只需要从左到右考察,某个城市如果供电站树木达不到目标,就在距离它右侧 + r 位置上加一座供电站即可。这里是一个贪心,我们很容易理解,既然是从左到右考察,把供电站摆放在最右侧是最优的。至于快速求解一个城市的可达的供电站数量,可以借助一个滑动窗口求和完成。

class Solution {
public:
    long long maxPower(vector<int>& stations, int r, int k) {
        int n = stations.size();
        // 二分搜索的左右边界,右边界(最大可能的供电站数量)可以取为供电站总数 + k,或者用一个固定的大数字也可以,比如 2E10
        // 注意本题供电站数量可以超过 INT_MAX 需要使用 long long 也即 int64
        long long left = 0, right = accumulate(stations.begin(), stations.end(), 0LL) + k;
        
        while(left < right) {
            // 我们有可能在一些城市新建供电站,因此拷贝一份城市供电站数量作为初始值
            // 由于计算逻辑,供电站数量可以超过 INT_MAX,long long 保险
            vector<long long> st(stations.begin(), stations.end());
            // 求和窗口中总供电站数量,初始化为左侧 r - 1 个城市总供电站数量
            long long total = accumulate(st.begin(), st.begin() + r, 0LL);
            // 二分法本轮检查 mid 个数的供电站能否达到
            // used 是已经新建的供电站数量
            long long mid = (left + right + 1) / 2, used = 0;

            for(int i = 0; i < n and used <= k; i++) {
                // 更新滑动窗口,右侧加入,左侧弹出
                total += i + r < n ? st[i + r] : 0;
                total -= i - r - 1 >= 0 ? st[i - r - 1] : 0;
                // 需要新建的供电站数量
                long long needed = mid - total;
                if(needed > 0) {
                    // 新建供电站,注意这里修改了每座城市中的电站数量 st
                    // 滑动窗口弹出时需要使用这里修改之后的 st 而不是初始值
                    // 小心这个位置的逻辑可以导致 st 中的 value 大于 INT_MAX
                    used += needed;
                    total += needed;
                    st[min(i + r, n - 1)] += needed;
                }
            }
            
            // 根据检查成功与否,更新二分左右边界
            if(used <= k) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }
        return left;
    }
};