单周赛 250 题解

题外话:自己在 okr 中立了个 flag,希望在 7-8 双月逐渐熟练掌握 python, go,所以在之后的周赛题解中,我会逐渐引入 python, go 的代码,有兴趣的小伙伴可以一起学习

本次周赛着重讲解一下第三题,前面两题一笔带过,最后一题涉及到 \(01\ trie\) 树,后续单独出一篇文章讲解

本次周赛知识点:字符串,数学,上取整,动态规划,高性能,前缀后缀

可以输入的最大单词数

给定一个字符串 \(w\) 表示一串单词,给定一个字符串 \(b\) 表示破坏的字母,对于 \(w\) 中的所有单词,如果包含 \(b\) 中的字母,那么该单词不可用,计算可用的单词数

题解

用一个数组存放 w 中的单词,用桶存放 b 中的字符,循环判断即可

// cpp
class Solution {
public:
    int canBeTypedWords(string t, string b) {
        vector<string> w;
        string temp = "";
        int bucket[27] = {0};
        for (int i = 0; i < b.size(); ++i) bucket[b[i] - 'a']++;
        for (int i = 0; i <= t.size(); ++i) {
            if (!(t[i] >= 'a' && t[i] <= 'z')) w.push_back(temp), temp = "";
            else temp += t[i];
        }
        int ans = 0;
        for (auto i: w) {
            int f = 0;
            for (int j = 0; j < i.size(); ++j) if (bucket[i[j] - 'a']) {f = 1; break;}
            if (!f) ans++;
        }
        return ans;
    }
};
# py
class Solution:
    def canBeTypedWords(self, t: str, b: str) -> int:
        st = set(b)
        ans = 0
        for word in t.split():
            flag = 0
            for ch in word:
                if ch in st:
                    flag = 1
                    break
            if not flag:
                ans += 1
        return ans
// go
func canBeTypedWords(t string, b string) (ans int) {
    for _, word := range strings.Split(t, " ") {
        if !strings.ContainsAny(word, b) {
            ans++
        }
    }
    return ans
}

新增的最少台阶数

给一个严格递增的数组 \(r\),表示台阶的高度,给一个正整数 \(dist\) 表示爬一次最高能上的台阶高度,初始时站在高度 \(0\) 位置,计算最少还需要放置几个台阶,使得可以爬到最后一个台阶

例如 r = [1, 3, 5, 10], dist = 2,需要放置 2 个台阶,高度分别为 7, 9(方案不唯一)

例如 r = [3, 6, 8, 10], dist = 3,不需要放置台阶

例如 r = [3, 4, 6, 7], dist = 2,需要放置 1 个台阶,高度为 1

题解

对于任意两个台阶,高度分别为 \(i,\ j\),设高度差为 \(h\),则可以放 \(\lceil\frac{h}{dist}\rceil - 1\) 个台阶

因此我们计算出差分数组 \(d\),依次做判断即可

在实现 \(\lceil\frac{a}{b}\rceil - 1\) 时,使用 (a - 1) / b 即可

// cpp
class Solution {
public:
    int addRungs(vector<int>& r, int dist) {
        int n = r.size();
        vector<int> d(n);
        d[0] = r[0];
        for (int i = 1; i < n; ++i) {
            d[i] = r[i] - r[i - 1];
        }
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            if (d[i] % dist == 0) ans += d[i] / dist - 1;
            else ans += d[i] / dist;
        }
        return ans;
    }
};
# py
class Solution:
    def addRungs(self, r: list[int], dist: int) -> int:
        pre = ans = 0
        for i in r:
            h = i - pre
            ans += (h - 1) // dist
            pre = i
        return ans
// go
func addRungs(r []int, dist int) (ans int) {
    pre := 0
    for _, h := range r {
        d := h - pre
        ans += (d - 1) / dist
        pre = h
    }
    return ans
}

扣分后的最大得分

给定一个 \(m\times n\) 的矩阵 \(p\),每一个格子有一个权值 \(p[i][j]\)

现在从第一行到最后一行,每一行选一个格子,收益为权值和

但是,对于第 \(i\) 行和第 \(i - 1\) 行,如果分别选了 \(j,\ k\) 列,那么收益减去 \(\mid j - k\mid\)

数据保证

\(1\leq m,\ n,\ m\times n\leq 10^5\)

\(0\leq p[i][j]\leq 10^5\)

题解

考虑二维动态规划,定义 \(dp[i][j]\) 表示走到 \((i,\ j)\) 位置的收益最大值,则状态转移方程为

\[dp[i][j]= \max\left\{dp[i][j],\ dp[i - 1][k] +p[i][j] - \mid j - k\mid\right\} \]

其中 \(0\leq k < n\)

但是我们需要枚举 \(i,\ j,\ k\),复杂度为 \(O(nmk)\),不能接受,因此考虑优化决策,对于状态转移方程,我们发现实际上要计算的是

\[\left\{\begin{matrix} dp[i - 1][k] + k + p[i][j] - j, & j\geq k\\ dp[i - 1][k] - k + p[i][j] + j, & j < k \end{matrix}\right. \]

对于当前决策,我们只需要分别计算出 \(j\geq k\) 部分的最大值和 \(j < k\) 部分的最大值即可

时间复杂度 \(O(nm \log n)\) 算法

我们用数组 \(h_1,\ h_2\) 分别维护两个值,具体来说

\[h_1[j] = dp[i - 1][k] + k + p[i][j] - j\\ h_2[j] = dp[i - 1][k] - k + p[i][j] + j\\ \]

然后用两棵红黑树维护二元组 \((j,\ h_1[j]),\ (j,\ h_2[j])\),这样便可以在 \(O(\log n)\) 时间计算出两段决策的最大值,在 c++ 中,我选用 set 来实现,需要重载运算符

随着决策 \(j\) 指针右移,第一棵红黑树中要删去节点 \((j,\ h_1[j])\),第二颗红黑树中要插入节点 \((j,\ h_2[j])\),时间复杂度均为 \(O(\log n)\)

因此总的时间复杂度为 \(O(nm \log n)\)

// cpp
typedef long long LL;
struct node{
    int idx;
    LL val;
    node() {}
    node(int _idx, LL _val):
        idx(_idx), val(_val) {}
};
bool operator< (const node &x, const node &y) {
    if (x.val == y.val) return x.idx < y.idx;
    return x.val > y.val;
}

class Solution {
public:
    long long maxPoints(vector<vector<int>>& a) {
        int m = a.size();
        int n = a[0].size();
        vector<vector<LL>> dp(m, vector<LL>(n));
        for (int j = 0; j < n; ++j) {
            dp[0][j] = a[0][j];
        }
        for (int i = 1; i < m; ++i) {
            vector<LL> h1(n), h2(n);
            set<node> st1, st2;
            for (int j = 0; j < n; ++j) {
                h1[j] = dp[i - 1][j] - j;
                h2[j] = dp[i - 1][j] + j;
                st1.insert(node(j, h1[j]));
            }
            for (int j = 0; j < n; ++j) {
                if (st1.size()) {
                    auto x = st1.begin();
                    dp[i][j] = max(dp[i][j], x->val + a[i][j] + j);
                }
                if (st2.size()) {
                    auto y = st2.begin();
                    dp[i][j] = max(dp[i][j], y->val + a[i][j] - j);
                }
                st1.erase(node(j, h1[j]));
                st2.insert(node(j, h2[j]));
                /*
                for (int k = 0; k < n; ++k) {
                    dp[i][j] = max(dp[i][j], dp[i - 1][k] + a[i][j] - abs(j - k));
                }
                */
            }
        }
        LL ans = 0;
        for (int j = 0; j < n; ++j) ans = max(ans, dp[m - 1][j]);
        return ans;
    }
};

时间复杂度 \(O(nm)\) 算法

\(pre[j]\) 维护 \(\max\left\{dp[i - 1][k] + k\mid 0\leq k\leq j\right\}\)

\(suf[j]\) 维护 \(\max\left\{dp[i - 1][k] - k\mid j\leq k\leq n - 1\right\}\)

则状态转移方程更新为

\[dp[i][j] = \max\left\{dp[i][j],\ \max\left\{pre[j] - j,\ suf[j] + j\right\} + p[i][j]\right\} \]

同时使用滚动数组把 \(dp\) 数组的第一维状态优化掉

总的时间复杂度为 \(O(nm)\),空间复杂度为 \(O(n)\)

注意此题爆 int,不开 long long 见祖宗

// cpp
typedef long long LL;
class Solution {
public:
    long long maxPoints(vector<vector<int>>& a) {
        int m = a.size(), n = a[0].size();
        vector<LL> dp(n);
        vector<LL> pre(n), suf(n);
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (!i) dp[j] = a[i][j];
                else {
                    LL x = max(pre[j] - j, suf[j] + j) + a[i][j];
                    dp[j] = max(dp[j], x);
                }
            }
            pre[0] = dp[0] + 0;
            suf[n - 1] = dp[n - 1] - (n - 1);
            for (int j = 1; j < n; ++j)
                pre[j] = max(pre[j - 1], dp[j] + j);
            for (int j = n - 2; j >= 0; --j)
                suf[j] = max(suf[j + 1], dp[j] - j);
        }
        LL ans = *max_element(dp.begin(), dp.end());
        return ans;
    }
};

使用前后缀代替红黑树,代码更好写了,运行效率也更快

posted @ 2021-07-25 22:13  徐摆渡  阅读(45)  评论(0编辑  收藏  举报