前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >周赛不讲武德出博弈论,一起用动态规划赌一赌

周赛不讲武德出博弈论,一起用动态规划赌一赌

作者头像
ACM算法日常
发布2021-06-16 16:15:02
6090
发布2021-06-16 16:15:02
举报
文章被收录于专栏:ACM算法日常

本次周赛不讲武德,出博弈论来为难我们这些老同志。

他说对不起,说他是乱出的,他可不是乱出的啊,分明是有备而来,先是一个动态规划,再是一个二分答案,最后还搞个博弈论,这好吗?这不好!算法要以和为贵,不要搞窝里斗,谢谢朋友们!

说正经的,本次周赛涉及知识点:动态规划,前缀和,树状数组,博弈论,二分答案,下面一起来看一看

哪种连续子字符串更长

给你长为

n

的二进制字符串 s

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

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

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

数据规定

1\leq n\leq10^5

题解

定义

dp_{1}\left[i\right]

表示到

i

位置,连续

0

子串的最大长度

定义

dp_{2}\left[i\right]

表示到

i

位置,连续

1

子串的最大长度

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

O(n)

事实上,可以滚动掉状态,使用两个变量代替

dp

数组,从而将空间复杂度优化到

O(1)
代码语言:javascript
复制
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

,二分左边界扩大

代码语言:javascript
复制
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\n", 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 + a\leq j\leq min\left\{i + b,\ n - 1\right\}
s\left[j\right] = 0

如果可以到达

n - 1

处,返回

true

,否则返回

false

数据保证

2\leq a\leq b\leq n\leq 10^5

题解

定义

dp\left[i\right]

表示能否跳到第

i

个位置

考虑前继状态转移到当前状态,即

dp\left[i - b\right],\ dp\left[i - b + 1\right], ..,\ dp\left[i - a\right]\rightarrow dp\left[i\right]

也就是说,对于

\forall j \in \left[i - b, i - a\right]

,只要

\exists j,\ s.t.\ dp\left[j\right] = 1

,那么

dp\left[i\right]

1

可以通过判断区间和

\sum\limits_{j = i - b}^{i - a}dp\left[j\right]

是否为

0

来完成上述转移,因此我们需要在转移的过程中维护前缀和,时间复杂度

O(n)

如果从当前状态转移到后继状态,即

dp\left[i\right]\rightarrow dp\left[i + a\right],\ ..,\ dp\left[i + b\right]

,需要对区间

\left[i + a,\ i + b\right]

作区间修改操作,转移的时候使用单点查询,有以下几种处理方式

  • 使用差分数组,转移的过程维护查分数组的前缀和,便可以得到当前点是否可以到达,时间复杂度
O(n)
  • 使用树状数组或者线段树,直接区间修改,转移到当前点使用单点查询,时间复杂度
O(nlogn)
代码语言:javascript
复制
/* 前继状态转移到当前,维护前缀和 */
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];
    }
};
代码语言:javascript
复制
/* 当前状态转移到后继状态,维护差分数组与前缀和 */
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

个石子获得的分数为

\sum\limits_{i = 1}^{x}a\left[i\right]

,这个可以用前缀和

pre

维护

dp\left[i\right]

表示可选石子范围为

[i,\ n)

时,

A

B

的分差最大值

考虑

A

是否选择

i

石子

  • 不选择,那么
A

要在

[i + 1,\ n)

内进行选择,因此有

dp\left[i\right] = dp\left[i + 1\right]
  • 选择,那么
A

获得分数

pre\left[i\right]

,同时

B

[i + 1, n)

内选择,因此

dp\left[i\right] = pre[i] - dp\left[i + 1\right]

所以

dp\left[i\right] = max\left\{dp\left[i + 1\right],\ pre[i] - dp\left[i + 1\right]\right\}

博弈论

dp

通常倒序转移,最后返回

dp\left[1\right]

数据保证

1\leq n\leq 10^5
代码语言:javascript
复制
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

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ACM算法日常 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 哪种连续子字符串更长
    • 题解
    • 准时到达的列车最小时速
      • 题解
      • 跳跃游戏 VII
        • 题解
        • 石子游戏 VIII
          • 题解
          • 后记
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档