zoukankan      html  css  js  c++  java
  • 单周赛 242 题解

    本次周赛涉及知识点:动态规划,前缀和,树状数组,博弈论,二分答案

    哪种连续子字符串更长

    给你长为 (n) 的二进制字符串 s

    如果字符串中由 1 组成的 最长子串严格长于0 组成的 最长子串,返回 true,否则,返回 false

    例如,s = "110100010" 中,由 1 组成的最长连续子字符串的长度是 2 ,由 0 组成的最长连续子字符串的长度是 3

    注意,如果字符串中不存在 0 ,此时认为由 0 组成的最长连续子字符串的长度是 0 。字符串中不存在 1 的情况也适用此规则。

    数据规定

    (1leq nleq10^5)

    题解

    定义 (dp_{1}left[i ight]) 表示到 (i) 位置,连续 (0) 子串的最大长度

    定义 (dp_{2}left[i ight]) 表示到 (i) 位置,连续 (1) 子串的最大长度

    转移完毕,维护两个最大值,最后比较即可,时间复杂度 (O(n))

    事实上,可以滚动掉状态,使用两个变量代替 (dp) 数组,从而将空间复杂度优化到 (O(1))

    class Solution {
    public:
        bool checkZeroOnes(string s) {
            int n = s.length();
            vector<int> dp1(n, 0);
            vector<int> dp2(n, 0);
            for (int i = 0; i < n; ++i) {
                if (s[i] == '1') {
                    dp1[i] = 0, dp2[i] = 1;
                    if (i > 0 && s[i] == s[i - 1]) dp2[i] = max(dp2[i - 1] + 1, dp2[i]);
                }
                else {
                    dp1[i] = 1, dp2[i] = 0;
                    if (i > 0 && s[i] == s[i - 1]) dp1[i] = max(dp1[i - 1] + 1, dp1[i]);
                }
            }
            int ans1 = 0, ans2 = 0;
            for (int i = 0; i < n; ++i) {
                ans1 = max(ans1, dp1[i]);
                ans2 = max(ans2, dp2[i]);
            }
            //cout << ans1 << ' ' << ans2 << endl;
            return ans1 < ans2;
        }
    };
    

    准时到达的列车最小时速

    给你一个浮点数 hour ,表示你到达办公室可用的总通勤时间。

    要到达办公室,你必须按给定次序乘坐 n 趟列车。

    给定一个长度为 n 的整数数组 dist ,其中 dist[i] 表示第 i 趟列车的行驶距离

    每趟列车均只能在整点发车,所以你可能需要在两趟列车之间等待一段时间。

    例如,第 1 趟列车需要 1.5 小时,那你必须再等待 0.5 小时,搭乘在第 2 小时发车的第 2 趟列车。

    返回能满足你准时到达办公室所要求全部列车的 最小正整数 时速,如果无法准时到达,则返回 -1

    生成的测试用例保证答案不超过 (10^7) ,且 hour 的小数点后最多存在两位数字。

    题解

    考虑二分答案,然后 check

    具体来讲,二分速度,对于每一个速度 (res),我们计算一下通勤的时间 (ans),如果 (ans < hour),返回 (true),二分右边界缩小,否则返回 (false),二分左边界扩大

    class Solution {
    public:
        #define INF 0x3f3f3f3f
        bool check(int res, vector<int> &vec, double hour) {
            double ans = 0;
            int n = vec.size();
            for (int i = 0; i < n; ++i) {
                int temp = vec[i];
                if (i < n - 1) {
                    ans += ceil(double(temp) / double(res));
                }
                else ans += double(temp) / double(res);
                //printf("%.6f
    ", ans);
            }
            return ans <= hour;
        }
        int minSpeedOnTime(vector<int>& vec, double hour) {
            int L = 1, R = INF;
            int ans = -1;
            while (L <= R) {
                int mid = L + R >> 1;
                //cout << L << ' ' << mid << ' ' << R << endl;
                if (check(mid, vec, hour)) {
                    R = mid - 1;
                    ans = mid;
                }
                else L = mid + 1;
            }
            return ans;
        }
    };
    

    跳跃游戏 VII

    给定一个长为 (n) 下标从 (0) 开始的 (01) 串,保证第一个位置一定是 (0)

    给定两个正整数 (a, b),当同时满足以下条件时,你可以从下标 (i) 转移到下标 (j)

    • (i + aleq jleq minleft{i + b, n - 1 ight})
    • (sleft[j ight] = 0)

    如果可以到达 (n - 1) 处,返回 (true),否则返回 (false)

    数据保证

    (2leq aleq bleq nleq 10^5)

    题解

    定义 (dpleft[i ight]) 表示能否跳到第 (i) 个位置

    考虑前继状态转移到当前状态,即 (dpleft[i - b ight], dpleft[i - b + 1 ight], .., dpleft[i - a ight] ightarrow dpleft[i ight])

    也就是说,对于 (forall j in left[i - b, i - a ight]),只要 (exists j, s.t. dpleft[j ight] = 1),那么 (dpleft[i ight])(1)

    可以通过判断区间和 (sumlimits_{j = i - b}^{i - a}dpleft[j ight]) 是否为 (0) 来完成上述转移,因此我们需要在转移的过程中维护前缀和,时间复杂度 (O(n))

    如果从当前状态转移到后继状态,即 (dpleft[i ight] ightarrow dpleft[i + a ight], .., dpleft[i + b ight]),需要对区间 (left[i + a, i + b ight]) 作区间修改操作,转移的时候使用单点查询,有以下几种处理方式

    • 使用差分数组,转移的过程维护查分数组的前缀和,便可以得到当前点是否可以到达,时间复杂度 (O(n))
    • 使用树状数组或者线段树,直接区间修改,转移到当前点使用单点查询,时间复杂度 (O(nlogn))
    /* 前继状态转移到当前,维护前缀和 */
    class Solution
    {
    public:
        bool canReach(string s, int minJump, int maxJump)
        {
            int n = s.length();
            vector<int> dp(n + 7, 0), sum(n + 7, 0);
            dp[1] = 1;
            for (int i = 1; i < 1 + minJump; ++i) {
                sum[i] = sum[i - 1] + dp[i];
            }
            for (int i = 1 + minJump; i <= n; ++i)
            {
                if (s[i - 1] == '0')
                {
                    int L = max(1, i - maxJump), R = i - minJump;
                    if (sum[R] - sum[L - 1])
                        dp[i] = 1;
                }
                sum[i] = sum[i - 1] + dp[i];
            }
            return dp[n];
        }
    };
    
    /* 当前状态转移到后继状态,维护差分数组与前缀和 */
    class Solution
    {
    public:
        bool canReach(string s, int minJump, int maxJump)
        {
            int n = s.length();
            vector<int> d(2 * n + 7);
            d[0]++, d[1]--;
            int sum = 0;
            for (int i = 0; i < n; ++i) {
                sum += d[i];
                if (sum > 0 && s[i] == '0' || i == 0) {
                    int L = i + minJump, R = i + maxJump;
                    d[L]++, d[R + 1]--;
                }
            }
            return sum > 0 && s[n - 1] == '0';
        }
    };
    

    石子游戏 VIII

    (A)(B) 玩一个游戏,两人轮流操作,(A) 先手

    总共有 (n) 个石子排成一行。轮到某个玩家的回合时,如果石子的数目大于 (1) ,他将执行以下操作

    • 选择一个整数 (x > 1) ,并且 移除 最左边的 (x) 个石子。
    • 将移除的石子价值之  累加到该玩家的分数中。
    • 将一个 新的石子 放在最左边,且新石子的值为被移除石子值之和。
    • 当只剩下 一个 石子时,游戏结束。

    (A)(B) 的分数之差为 ((A) 的分数减去 (B) 的分数) 。 (A) 的目标是 最大化 分数差,(B) 的目标是 最小化 分数差。

    给你一个长度为 (n) 的整数数组 stones ,其中 stones[i] 是 从左边起 第 i 个石子的价值。请你返回在双方都采用 最优 策略的情况下,(A)(B) 的分数之差

    题解

    选取前 (x) 个石子获得的分数为 (sumlimits_{i = 1}^{x}aleft[i ight]),这个可以用前缀和 (pre) 维护

    (dpleft[i ight]) 表示可选石子范围为 ([i, n)) 时,(A)(B) 的分差最大值

    考虑 (A) 是否选择 (i) 石子

    • 不选择,那么 (A) 要在 ([i + 1, n)) 内进行选择,因此有 (dpleft[i ight] = dpleft[i + 1 ight])
    • 选择,那么 (A) 获得分数 (preleft[i ight]),同时 (B)([i + 1, n)) 内选择,因此 (dpleft[i ight] = pre[i] - dpleft[i + 1 ight])

    所以 (dpleft[i ight] = maxleft{dpleft[i + 1 ight], pre[i] - dpleft[i + 1 ight] ight})

    博弈论 (dp) 通常倒序转移,最后返回 (dpleft[1 ight])

    数据保证

    (1leq nleq 10^5)

    class Solution {
    public:
        int stoneGameVIII(vector<int>& stones) {
            int n = stones.size();
            vector<int> pre(n), dp(n);
            pre[0] = stones[0];
            for (int i = 1; i < n; ++i) pre[i] = pre[i - 1] + stones[i];
            dp[n - 1] = pre[n - 1];
            for (int i = n - 2; i >= 1; --i) {
                dp[i] = max(dp[i + 1], pre[i] - dp[i + 1]);
            }
            return dp[1];
        }
    };
    

    后记

    博弈论 (dp) 还是挺难的,看了题解想半天才明白,不过似乎也有对应的套路,还是要多刷题

  • 相关阅读:
    react-动画
    react-json渲染
    SQL 只取重复记录一条记录并且是最小值
    SQL 函数:树结构指定父节点遍历所有的子节点
    EasyUI treegrid 删除一条或多条记录
    String.Format数字格式化输出 {0:N2} {0:D2} {0:C2} (转)
    EasyUI 左,右(上、下)布局
    DataTable 树形构造加全部
    easyui datagrid 格式化列显示两位小数、千分位
    c# 判断文件是否已使用
  • 原文地址:https://www.cnblogs.com/ChenyangXu/p/14826945.html
Copyright © 2011-2022 走看看