Educational Codeforces Round 167(Div.2) A~D

A.Catch the Coin(思维)

题意:

Monocarp 参观了一家有街机柜的复古街机俱乐部。在那里,他对"抓硬币"游戏机产生了好奇。

游戏非常简单。屏幕上的坐标网格是这样的

  • X X X轴从左到右;
  • Y Y Y轴从下往上;
  • 屏幕中心的坐标为 ( 0 , 0 ) (0,0) (0,0)

游戏开始时,角色位于中心位置,屏幕上出现了 n n n枚硬币,其中第 i i i枚硬币的坐标为 ( x i , y i ) (x_i,y_i) (xi,yi)。所有硬币的坐标都不相同,也不等于 ( 0 , 0 ) (0,0) (0,0)

在一秒钟内,可以向八个方向之一移动角色。如果字符位于坐标 ( x , y ) (x,y) (x,y),那么它可能最终到达坐标 ( x , y + 1 ) (x,y+1) (x,y+1) ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1) ( x + 1 , y ) (x+1,y) (x+1,y) ( x + 1 , y − 1 ) (x+1,y-1) (x+1,y1) ( x , y − 1 ) (x,y-1) (x,y1) ( x − 1 , y − 1 ) (x-1,y-1) (x1,y1) ( x − 1 , y ) (x-1,y) (x1,y) ( x − 1 , y + 1 ) (x-1,y+1) (x1,y+1)中的任意一个。

如果角色最终在坐标处获得了一枚硬币,那么Monocarp就会收集这枚硬币。

在Monocarp移动之后,所有硬币都会下降 1 1 1,即从 ( x , y ) (x,y) (x,y)移动到 ( x , y − 1 ) (x,y-1) (x,y1)。我们可以假设游戏场地在所有方向上都是无限的。

Monocarp想要收集至少一枚硬币,但却无法决定收集哪枚硬币。请帮助他确定能否收集到每一枚硬币。

分析:

设一个硬币与原点的 x x x轴方向距离为 d d d。最优思路肯定是在开局的 d d d秒内走到与硬币 x x x坐标相同、 y y y坐标为 − d -d d的位置。如果此时硬币已经掉到这个位置下方那就收集不到了,否则就肯定可以接到。

代码:

#include<bits/stdc++.h>

using namespace std;

int main() {
    int n, x, y;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> x >> y;
        int dist = (x == 0 ? 0 : abs(x));
        int yy = -dist;
        if (y - dist >= yy - 1)
            cout << "YES" << endl;
        else
            cout << "NO" << endl;
    }
    return 0;
}

B.Substring and Subsequence(贪心)

题意:

给你两个字符串 a a a b b b,这两个字符串都由小写拉丁字母组成。

字符串的子串是从原始字符串中删除几个(可能是零)字符后得到的字符串。字符串的子串是该字符串的连续子串。

例如,考虑字符串 a b a c abac abac

  • a 、 b 、 c 、 a b 、 a a 、 a c 、 b a 、 b c 、 a b a 、 a b c 、 a a c 、 b a c a、b、c、ab、aa、ac、ba、bc、aba、abc、aac、bac abcabaaacbabcabaabcaacbac a b a c abac abac是其子序列;
  • a 、 b 、 c 、 a b 、 b a 、 a c 、 a b a 、 b a c a、b、c、ab、ba、ac、aba、bac abcabbaacababac a b a c abac abac是它的子串。

你的任务是计算包含 a a a作为子串和 b b b作为子序列的字符串的最小可能长度。

分析:

考虑贪心,答案一定包含这两个字符串并加上一些字符,为使答案最短,要让添加的字符最少,即让 a a a b b b的公共子序列尽可能长,所以答案即为两个字符串长度之和减去最长公共子序列的长度。

观察数据范围较小,暴力寻找最长公共子序列,每次从 b b b的的一个字符开始向后与 a a a匹配, s u m sum sum记录当前区间的最长公共子序列, s s s记录 a a a b b b的最长公共子序列。

代码:

#include<bits/stdc++.h>

using namespace std;

void solve() {
    string a, b;
    cin >> a >> b;
    int n = a.size();
    int m = b.size();
    int s = 0;
    for (int i = 0, sum = 0; i < m; i++, s = max(s, sum), sum = 0)
        for (int j = 0, t = i; j < n; j++)
            if (a[j] == b[t]) {
                t++;
                sum++;
            }
    cout << n + m - s << endl;
}

int main() {
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

C.Two Movies(贪心)

题意:

某电影公司发行了 2 2 2部电影。有 n n n人观看了这 2 2 2部电影。我们知道每个人对第一部电影的态度(喜欢、中立或不喜欢)以及对第二部电影的态度。

如果要求某人为电影留下评论,那么

  • 如果这个人喜欢这部电影,他就会留下好评,这部电影的评分就会增加 1 1 1
  • 如果此人不喜欢这部电影,则会留下差评,电影评分将降低 1 1 1
  • 否则,他们会留下中评,电影评分不会改变。

每个人都会评论一部电影,您可以为每个人选择评论哪部电影。

公司的评分是两部电影评分的最小值。您的任务是计算公司可能获得的最高评分。

分析:

本题我们采用贪心的思路,如果某人对两部电影评分不一样,显然取评分高的那一个。即如果是 1 1 1 0 0 0 1 1 1 − 1 −1 1,那么显然取 1 1 1。如果是 0 0 0 − 1 −1 1,显然取 0 0 0

下面讨论评分一样的情况。对于 0 0 0 0 0 0,取哪一个都没有影响,直接忽略。对于 1 1 1 1 1 1,我们记录这种人的个数,在处理完评分不一样的人之后统一处理。由于我们要使最小值最大,所以优先将这种人的增加评分给较小的一部电影。否则不会影响最小值,显然不是最优方法。对于 − 1 −1 1 − 1 −1 1,同理,我们记录这种人的个数,在处理完评分不一样的人之后统一处理。由于我们要使最小值最大,所以优先将这种人的减少评分给较大的一部电影。否则会减小最小值,不是最优情况。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const int MOD = 998244353;
LL n, a[300000], b[300000];

void solve() {
    LL x = 0, y = 0, cnt1 = 0, cnt2 = 0;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++)
        cin >> b[i];
    for (int i = 1; i <= n; i++) {
        if (a[i] > b[i])
            x += a[i];
        else if (a[i] < b[i])
            y += b[i];
        else if (a[i] == 1 && b[i] == 1)
            cnt1++;
        else if (a[i] == -1 && b[i] == -1)
            cnt2++;
    }
    while (cnt1) {
        if (x <= y)
            x++;
        else
            y++;
        cnt1--;
    }
    while (cnt2) {
        if (x >= y)
            x--;
        else
            y--;
        cnt2--;
    }
    cout << min(x, y) << endl;
}

int main() {
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

D.Smithing Skil(贪心、动态规划)

题意:

您正在玩一款著名的电脑游戏,在这款游戏中,您可以提升各种技能的等级。今天,你的重点是 "铸造"技能。你的战术显而易见:用金属锭锻造武器,然后将其熔化,部分返回材料。简单来说,每制造一件物品,你就能获得 1 1 1点经验值,而每熔化一件物品,你也能获得 1 1 1点经验值。

可以锻造的武器有 n n n种,金属锭有 m m m种。

花费 a i a_i ai个同类金属锭,你就可以打造一把 i i i类的武器。熔化一件(你之前制作过的) i i i阶级的武器会为你带来 b i b_i bi块与之相同类型的金属锭。

你有 c j c_j cj j j j类型的金属锭,而且你知道你可以用任何金属类型制作任何类型的武器。武器等级和金属类型的每种组合都可以使用任意次数。

制作和熔炼武器最多可以获得多少经验值?

分析:

观察题目发现要对每种金属求出最多能获得的经验数,然后相加。此外,一种装备锻造了之后一定会将其融化掉,因为熔掉获得材料和经验。我们注意到 a i − b i a_i−b_i aibi最小的装备是贡献最高的,因为消耗材料最少。在所有贡献最高的装备里面我们可以随便选取一种出来针对同一种金属不停地锻造再熔掉,直到因为剩余金属量 < a i <a_i <ai而不能锻造为止。

观察数据范围发现 a i ≤ 1 0 6 a_i≤10^6 ai106,当某种金属剩余量为 x x x时,我们的最优方案是在所有 a i ≤ x a_i≤x aix的装备中选贡献最高的锻造。因此我们可以对所有 ≤ 1 0 6 ≤10^6 106的金属剩余量用一个简单的 d p dp dp预处理出这个剩余量所能挣到的经验。对于较大的剩余量 c k ( k ∈ [ 1 , m ] ) c_k(k∈[1,m]) ck(k[1,m]),先用贡献最高的装备把剩余量消耗到 1 0 6 10^6 106以下,然后调用预处理的值即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const int N = 1000010;
const int MOD = 998244353;

void sol(LL &a, LL b) {
    if (a > b)
        a = b;
}

LL n, m, a[N], b[N], c[N], add[N], dp[N];

int main() {
    ios::sync_with_stdio(false);
    for (int i = 0; i < N; i++) add[i] = 1e18;
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> a[i];
    for (int i = 0; i < n; i++) {
        cin >> b[i];
        b[i] = a[i] - b[i];
        sol(add[a[i]], b[i]);
    }
    for (int i = 0; i < m; i++) cin >> c[i];
    LL mn = 1e18;
    for (LL i = 1; i <= 1000003; ++i) {
        sol(mn, add[i]);
        if (mn <= i) dp[i] = dp[i - mn] + 1;
    }
    pair<LL, LL> opt = make_pair(1e18, 1e18);
    for (int i = 0; i < n; i++) opt = min(opt, make_pair(b[i], a[i]));
    LL ans = 0;
    for (int i = 0; i < m; i++) {
        if (c[i] >= opt.second) {
            ans += (c[i] - opt.second + opt.first - 1) / opt.first;
            c[i] -= (c[i] - opt.second + opt.first - 1) / opt.first * opt.first;
        }
        ans += dp[c[i]];
    }
    cout << ans * 2 << endl;
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值