Codeforces Round 927(Div.3) A~F

A.Thorns and Coins(模拟)

题意:

在你的电脑宇宙之旅中,你偶然发现了一个非常有趣的世界。这是一条有 n n n个连续单元格的路径,每个单元格可能是空的,也可能包含荆棘或一枚硬币。在一次移动中,你可以沿着这条路径移动一个或两个单元格,前提是目的地单元格不包含荆棘(并且属于这条路径)。如果移动到有硬币的格子,就会拾起它。

这里,绿色箭头代表合法移动,红色箭头代表非法移动。

你想要收集尽可能多的硬币。如果你从路径最左边的单元格开始,请找出你最多可以收集到的硬币数量。

分析:

根据题意每一步只能走一步或者两步,如果有连续的两个荆棘就不能走了,在不能走之前是可以把路上的金币全捡起来,所以只需要边捡金币边判断是否能继续走下即可。

代码:

#include<bits/stdc++.h>

using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        string s;
        cin >> s;
        int cnt = 0, ans = 0;
        for (int i = 0; i < n; i++) {
            if (s[i] == '*')
                ++cnt;
            else
                cnt = 0;
            if (cnt == 2)
                break;
            if (s[i] == '@')
                ++ans;
        }
        cout << ans << endl;
    }
    return 0;
}

B.Chaya Calendar(模拟)

题意:

查亚部落相信世界末日有 n n n个征兆。随着时间的推移,人们发现每隔 a i a_i ai年(即 a i a_i ai年、 2 ⋅ a i 2\cdot a_i 2ai 年、 3 ⋅ a i 3\cdot a_i 3ai 年、 … \dots )就会出现 i i i个征兆。

根据传说,世界末日必须按顺序出现。也就是说,首先要等待第一个征兆出现,然后严格按照这个顺序,第二个征兆才会出现,以此类推。也就是说,如果第 i i i个征兆在 x x x年出现,那么部落就会从 x + 1 x+1 x+1年开始等待第 ( i + 1 ) (i+1) (i+1)个征兆的出现。

计算哪一年会出现第 n n n个征兆。

分析:

模拟一下每个征兆出现的年数,假设算出了前 i − 1 i−1 i1个的征兆,最后一个征兆发生的年份是 n w nw nw,那么第 i i i个发生征兆的年份是大于 n w nw nw的最小倍数。根据题意直接从模拟即可,从第二个元素开始让每个元素严格大于前一个元素。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL N = 3e5 + 10;
int n, a[N];

int main() {
    int t = 1;
    cin >> t;
    while (t--) {
        cin >> n;
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
            if (i > 1) {
                if (a[i] <= a[i - 1])
                    a[i] = (a[i - 1] / a[i] + 1) * a[i];
            }
        }
        cout << a[n] << endl;
    }
    return 0;
}

C.LR-remainders(递归)

题意:

给你一个长度为 n n n的数组 a a a、一个正整数 m m m和一串长度为 n n n的命令。每条命令要么是字符 “L”,要么是字符"R"。

按照字符串 s s s中的顺序处理所有 n n n命令。处理一条命令的步骤如下:

  • 首先,输出数组 a a a中所有元素除以 m m m后的乘积余数。
  • 然后,如果命令是"L",则从数组 a a a中删除最左边的元素;如果命令是"R",则从数组 a a a中删除最右边的元素。

注意,每次移动后,数组 a a a的长度都会减少 1 1 1,处理完所有命令后,数组 a a a将为空。

按照字符串 s s s中的顺序(从左到右)处理所有命令。

分析:

本题如果直接按题意进行模拟会超时,我们倒着做,考虑递归,先沿着操作递归到终点。然后返回的时候返回区间乘积的余数,顺便记录每个位置的余数,之后顺序输出即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL N = 2e5 + 10;

int n, m, a[N];
string s;

vector<int> ans;

LL solve(int x, int l, int r) {
    if (x >= n)
        return 1;
    LL tmp;
    if (s[x] == 'L')
        tmp = a[l] * solve(x + 1, l + 1, r) % m;
    else
        tmp = a[r] * solve(x + 1, l, r - 1) % m;
    ans.push_back(tmp);
    return tmp;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        cin >> n >> m;
        for (int i = 1; i <= n; i++)
            cin >> a[i];
        cin >> s;
        ans.clear();
        solve(0, 1, n);
        for (auto it2 = ans.rbegin(); it2 != ans.rend(); it2++)
            cout << *it2 << " ";
        cout << endl;
    }
    return 0;
}

D.Card Game(模拟)

题意:

两名玩家正在玩一款在线纸牌游戏。游戏使用一副 32 32 32张牌。每张牌都有花色和等级。共有四种花色:梅花、方块、红心和黑桃。我们将分别用字符"C"、“D”、"H"和"S"对它们进行编码。共有 8 8 8个等级,依次递增:‘2’,‘3’,‘4’,‘5’,‘6’,‘7’,‘8’,‘9’。

每张牌都用两个字母表示:等级和花色。例如,红心 8 8 8表示为 8 H 8H 8H

游戏开始时,选择一种花色作为王牌花色

在每一轮游戏中,玩家都要这样出牌:第一名玩家将自己的一张牌放在桌上,第二名玩家必须用自己的一张牌击败这张牌。之后,两张牌都被移到弃牌堆。

如果两张牌的花色相同,且第一张牌的等级高于第二张牌,那么这张牌就能打败另一张牌。例如, 8 S 8S 8S可以打败 4 S 4S 4S。例如,如果王牌花色是梅花(“C”),那么 3 C 3C 3C可以击败 9 D 9D 9D。请注意,王牌只能被等级更高的王牌击败。

游戏中共进行了 n n n轮,因此弃牌堆中现在有 2 n 2n 2n张牌。你想重建游戏中的回合,但是弃牌堆中的牌是洗过的。请找出游戏中可能出现的 n n n个回合。

分析:

我们发现除了王牌以外其他不同花色之间不能分出胜负,所以将输入数据按照花色分类并排序,让除了王牌以外的花色尽可能战胜相同花色,如果这个花色数量为奇数,那么最后会剩余一张,在王牌里挑一张战胜它即可。如果模拟过程中发现王牌不够用则表明无解。

代码:

#include<bits/stdc++.h>

using namespace std;
int n;
char ch;

void solve() {
    cin >> n;
    n *= 2;
    cin >> ch;
    vector<string> op;
    map<char, vector<string> > mp;
    for (int i = 1; i <= n; i++) {
        string str;
        cin >> str;
        mp[str[1]].push_back(str);
    }
    vector<char> s;
    if (ch != 'C') s.push_back('C');
    if (ch != 'D') s.push_back('D');
    if (ch != 'H') s.push_back('H');
    if (ch != 'S') s.push_back('S');
    vector<pair<string, string>> ans;
    bool check = true;
    int pos = 0;
    sort(mp[ch].begin(), mp[ch].end());
    for (int i = 0; i < 3; i++) {
        sort(mp[s[i]].begin(), mp[s[i]].end());
        for (int j = 0; j < mp[s[i]].size(); j += 2) {
            if (j + 1 < mp[s[i]].size())
                ans.push_back({mp[s[i]][j], mp[s[i]][j + 1]});
            else {
                if (pos < mp[ch].size())
                    ans.push_back({mp[s[i]][j], mp[ch][pos++]});
                else {
                    check = false;
                    break;
                }
            }
        }
    }
    for (int i = pos; i < mp[ch].size(); i += 2) {
        ans.push_back({mp[ch][i], mp[ch][i + 1]});
    }
    if (check) {
        for (auto x: ans) {
            cout << x.first << " " << x.second << endl;
        }
    } else {
        cout << "IMPOSSIBLE" << endl;
    }
}

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

E.Final Countdown(前缀和)

题意:

你身处一个即将爆炸并摧毁地球的核实验室。必须在最后倒计时为零之前拯救地球。

倒计时由 n n n( 1 ≤ n ≤ 4 ⋅ 1 0 5 1\le n\le 4\cdot 10^5 1n4105)个机械指示器组成,每个指示器显示一位小数。你注意到,当倒计时的状态从 x x x变为 x − 1 x-1 x1时,并不是一蹴而就的。相反,每个数字的变化都需要 1 1 1秒钟。

因此,举例来说,如果倒计时显示 42 42 42,那么它将在一秒钟内变为 41 41 41,因为只有一位数发生了变化;但如果倒计时显示 2300 2300 2300,那么它将在三秒钟内变为 2299 2299 2299,因为最后三位数发生了变化。

找出倒计时归零前还剩多少时间。

分析:

考虑到只要有一位退位就会用掉一秒(假设把个位上的数减一也看作退一位),而减一的逆过程就是加一,被减数减 1 1 1退位的次数和减数加 1 1 1产生的进位的次数是相同的,因此我们把一个数不断减一到零产生的退位次数相当于给零加一不断加到这个数产生的进位次数。

而不断加 1 1 1,每个数位上会产生多少进位。以 12345 12345 12345举例,个位会变化 12345 12345 12345次,十位会变化 1234 1234 1234次,百位会变化 123 123 123次,千位变化 12 12 12次,万位变化 1 1 1次,总的变化次数就是 12345 + 1234 + 123 + 12 + 1 = 13715 12345+1234+123+12+1=13715 12345+1234+123+12+1=13715次。可以发现个位上的数相当于前五个数位上每个数的和模 10 10 10(多出来的数进位),十位上的数相当于前四位上每个数的和加上来自个位的进位模 10 10 10,同理百千万位上的数也是如此。

这提示我们可以用前缀和算出每个数位上的数,最后处理一下进位即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;

void solve() {
    int n;
    cin >> n;
    string s;
    cin >> s;
    for (int i = 0; i < n; i++) {
        if (s[i] != '0') {
            s = s.substr(i);
            break;
        }
    }
    n = s.size();
    s = ' ' + s;
    vector<LL> pre(n + 10, 0);
    for (int i = 1; i <= n; i++) {
        pre[i] = pre[i - 1] + (s[i] - '0');
    }
    string ans;
    LL res = 0;
    for (int i = n; i > 0; i--) {
        char c = (pre[i] % 10) + '0';
        ans += c;
        if (i > 1) {
            pre[i - 1] += pre[i] / 10;
        } else {
            res += pre[i] / 10;
        }
    }
    while (res) {
        char c = (res % 10) + '0';
        res /= 10;
        ans += c;
    }
    reverse(ans.begin(), ans.end());
    cout << ans << endl;
}

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

F.Feed Cats(动态规划)

题意:

在这个有趣的游戏中,你需要喂养来来往往的猫咪。游戏的关卡由 n n n步组成。有 m m m只猫;第 i i i只猫出现在 l i l_i li r i r_i ri(包括 r i r_i ri)步。在每一步中,可以喂养当前出现的所有猫咪,或者什么也不做。

如果喂同一只猫超过一次,它会暴饮暴食,你就会立即输掉游戏。你的目标是在不导致任何一只猫暴食的情况下喂食尽可能多的猫。

计算你能喂养的最大猫咪数量。

你需要从 1 1 1 n n n的线段中选择几个整数点,以便在给定的线段中,没有一条线段覆盖两个或更多选定点,并且让尽可能多的线段覆盖一个选定点。

分析:

d p i dp_i dpi: 考虑前 i i i个点的选取,能得到的最大线段数。

如果选第 i i i个点,那么我们要找到覆盖点的线段中,最小的左端点位置 i d x idx idx d p i = d p i d x − 1 + c n t dp_i=dp_{idx-1}+cnt dpi=dpidx1+cnt。其中, c n t cnt cnt为经过点 i i i的线段数量。

如果不选第 i i i个点,就是考虑前 i i i个点得到的最大答案 d p i = d p i − 1 dp_i=dp_{i-1} dpi=dpi1

代码:

#include<bits/stdc++.h>

using namespace std;

void solve() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> l(n + 1, vector<int>());
    vector<int> d(n + 10);
    for (int i = 1; i <= m; i++) {
        int a, b;
        cin >> a >> b;
        l[b].push_back(a);
        d[a]++;
        d[b + 1]--;
    }
    for (int i = 1; i <= n; i++) {
        d[i] += d[i - 1];
    }
    vector<int> w(n + 10, n + 1);
    for (int i = n; i; i--) {
        w[i] = min(i, w[i + 1]);
        for (auto x: l[i]) {
            w[i] = min(w[i], x);
        }
    }
    vector<int> dp(n + 1);
    for (int i = 1; i <= n; i++) {
        dp[i] = max(dp[i - 1], dp[w[i] - 1] + d[i]);
    }
    cout << dp[n] << endl;
}

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

赛后交流

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

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

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值