Codeforces Round 986 (Div. 2)补题

总结

还是掉分。加上上一场已近掉了100分。虽然的确有其他的原因,但是也说明了现在的学习方式是错误的。后边应该每周就一场力扣,一场cf。不然补题的时间太多了,应该花些时间去系统的练习一个板块的题目。先试一试效果怎么样。比赛地址:https://codeforces.com/contest/2028

补题和反思

A:Alice’s Adventures in ‘‘Chess’’

老实话,现在想起来第一题看完就该放弃这场的。可惜没有。

反思和做法:

这个题目第一眼看上去,真的是懵的。想过是不是要考虑a,b和行动的关系。会不会有什么互质啥的,就一定能够到达。但是又想到是第一题不该这么难才对。

后边看了数据范围才发现直接模拟就行了。因为n,a和b都只有10。那么就还是有一个就是什么时候停止了。第一时间的想法是,如果走的和(a,b)的距离太大就可以停止了。试了一试,错了。因为有可能最终又回到起点(0, 0),然后一直循环。最后没什么想法。直接模拟100次。

代码:

#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>

using namespace std;

int main() {
    int t;
    cin >> t;

    while (t -- ) {
        int n, a, b;
        cin >> n >> a >> b;

        string str;
        cin >> str;

        int x = 0, y = 0, flag = false;
        for (int j = 0; j < 100 * n; j ++ ) {
            int i = j % n;

            if (str[i] == 'E') {
                x += 1;
            }
            else if (str[i] == 'N') {
                y += 1;
            }
            else if (str[i] == 'W'){
                x -= 1;
            }
            else {
                y -= 1;
            }

            if (a == x && b == y) {
                flag = true;
                break;
            }
        }

        if (flag) {
            cout << "Yes" << endl;
        }
        else {
            cout << "No" << endl;
        }
    }

    return 0;
}

B:Alice’s Adventures in Permuting

虽然写的时候,写了很久将近一个小时。但是也反映了我的数学这个板块真的有点薄弱了。当然代码能力也不是很行(笑)。

做法和反思:

拿到这个题目之后,我就知道可能要遭罪了。因为数据范围是1e18,那必须是O(1)或则是 O(logN) 的时间复杂度。可以说是相当不擅长这种需要分情况讨论,而且要把每种情况想的十分清楚的题目。

正好再好好分析分析,希望下次遇见类似的别还是这么抓瞎。

看到题目之后,我先想过怎么样才会输出-1。正好样例中两个例子,看完之后我觉得应该是当 b 等于 0 的时候,就会返回-1。但是提交之后错了,又再详细看了样例解释,才反应过来。

首先对于n个数字,我们应该需要将其变为0 ~ n -1的一个排列。也就是最大数字是n - 1。
当b = 0时,此时 a 数组是 一个长为 n,且所有元素均为 c 的数组。对于这个数组,当n足够大时,其能生成的最大数字是 c + 1。因为之后,数组的最大值是c + 1 但是MEX是c + 2。操作之后,但是这是 数组的MEX 又变成了 c + 1,而最大值是c+2。这样就进入了循环。例如[1,0,0]→[2,0,0]→[1,0,0]→[2,0,0]。

这里给了我启发,当b = 0时,应该讨论 n - 1 和 c + 1的关系。

  1. 当n - 1 > c + 1时。会陷入上述的循环过程中,返回-1.
  2. 当n - 1 < c 时。那么我们只需要操作n次即可。
  3. 当c <= n - 1 <= c + 1时。我们只需要只需要操作 n - 1次。因为会跳过MEX=c。

当b != 0 时,a数组就是c,b + c,2*b + c … (n - 1)*b + c 。我的想法就是,先将c之前的数字填上。然后再考虑剩下的数字怎么做。分类讨论:

  1. 当c > n - 1 时。我们直接操作n次即可。
  2. 当c = n - 1 时。我们需要操作n - 1次即可。
  3. 当 c < n - 1时。我们可以先操作 c 次,将问题变为把 b + c, 2*b + c,…变为c+1,c+2,c+3…。注意这里只有(n - c - 1)个数字了。这里我们可以发现每b个数,我们就可以少操作一次。所以需要操作 c + (n - c - 1) % b + (n - c - 1) / b * (b - 1)。

代码:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    int t;
    cin >> t;

    while (t -- ) {
        long long n, b, c;
        cin >> n >> b >> c;

        if (b == 0) {
            if (n - 1 > c + 1) {
                cout << -1 << endl;
            }
            else {
                cout << n - (n - 1 >= c) << endl;
            }

            continue;
        }

        long long ans;
        if (c > n - 1) {
            ans = n;
        }
        else if (c == n - 1) {
            ans = n - 1;
        }
        else {
            ans = c + (n - c - 1) % b + (n - c - 1) / b * (b - 1);
        }
        cout << ans << endl;
    }

    return 0;
}

C:Alice’s Adventures in Cutting Cake

这个想出来还挺快,就是写代码太慢了。结束后几分钟写出来了。

做法

简单来说就是前后缀分解。如果你看了这里又知道什么是前后缀分解,那么可以试着重新再想一想这个题目。

我们可以预处理一个后缀数组post,其中post[i]表示,从post[i] ~ n能够分出 i 个符合要求的蛋糕。和一个前缀数组pre,其中pre[i],表示从1~pre[i],可以分出 i 个符合要求的蛋糕。

处理结束后,如果post的长度小于 m,那么表示无法分出 m 个符合要求的蛋糕。返回-1即可。

然后我们枚举前边分多少个蛋糕 i,那么后边需要分出 m - i个蛋糕。如果pre[i] < post[m - i],那么留给爱丽丝的就是(pre[i] 和 post[m - i]),这个范围中 a数组的和。可以通过前缀和实现O(1)的计算。最终返回爱丽丝能够得到的最大值即可。

这里我们可以一边计算pre,一边统计答案。但是写起来感觉比较麻烦。因为我就只这么写的(哭)。代码中我习惯从0开始。

代码:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    int t;
    cin >> t;

    while (t -- ) {
        int n, m, v;
        cin >> n >> m >> v;

        vector<long long> a(n), sum(n + 1, 0);
        for (int i = 0 ; i < n; i ++ ) {
            cin >> a[i];
            sum[i + 1] = sum[i] + a[i];
        }

        vector<int> post;
        long long now = 0;
        for (int i = n - 1; i >= 0; i -- ) {
            now += a[i];
            if (now >= v) {
                post.push_back(i);
                now = 0;
            }
        }

        if (post.size() < m) {
            cout << -1 << endl;
            continue;
        }

        now = 0;
        long long ans = sum[post[m - 1]];
        for (int i = 0, j = 0; i < n; i ++ ) {
            now += a[i];
            if (now >= v) {
                now = 0;
                j ++ ;
                if (j == m) {
                    ans = max(ans, sum[n] - sum[i + 1]);
                    break;
                }
                else {
                    int idx = post[m - j - 1];
//                    cout << idx << endl;
                    if (idx > i) {
//                        cout << sum[idx] - sum[i + 1] << endl;
                        ans = max(sum[idx] - sum[i + 1], ans);
                    }
                }
            }
        }

        cout << ans << endl;
    }

    return 0;
}

D:Alice’s Adventures in Cards

这个题属于后边写的,没思路。看到题解。不过题解写的有点难懂,看都看了好久才明白。我的想法错的很远,所以没什么好说的。不过这个题目加深了我对dp的认识。因为之前写的题目都比较裸,所以没发现,原来 dp 状态和转移真是五花八门。而且很多时候,转移方式没事那么直接,还需要一些贪心啥的来简化。

为什么这么做?

省流就是dp,用dp[i]表示当爱丽丝手中牌是 i 时,能否跳到n。
具体而言。从后向前做,每次分别判断在 q,k,j中是否有一个是否能够跳到n。如果可以,那么dp[i] = true。

那么这里就有一个问题了:如何判断 能否从 i 跳到 n?
我们拿q举例。如果能从在q中,从 i 跳到 n,我们需要找到一个 j,j > i && q[ j ] < q[ i ]。并且dp[ j ] = true,那么我们就可以从 i 跳到 n。

这里又牵扯出一个问题:对于 i,我该怎么找到这个 j ?
同样那q举例。我们是从后往前遍历的,所以,i 之前计算的所有dp[ j ] = true 的 j,都是满足 j > i && dp[ j ] = true 这两个条件的。所以只需要在这些 j 中找到满足q[j] < q[ i ]即可。但是如果每次都遍所有 j, 这样时间复杂度会是O(N^2)的。所以我们需要记录一个值,能够代表所有满足条件的 j。这里因为需要 q[ j ] < q[ i ]。那么我们只需要记录满足条件中的 最小的q[ j ] = minP 即可。那么我们只需要通过 q[ i ] 和 minP的大小关系,即可判断 在 q 中 i 能否跳到 n。

同样又有一个问题:如何维护 minP?
我们需要注意因为又q,k,j三个数组,所以我们也有三个minP,用 minP[i] 来区分。我们可以注意到,三个数组中,如果存在一个数组能够在 i 跳到 n。我们要用q[ i ] 和 minP[0],k[ i ] 和 minP[1],j[ i ] 和 minP[2],分别取一个min。来更新minP。为什么呐?因为我们能在 三个 数组中任意切换。只要有一个能够到 n,那么对于 i 之前的位置,只要能够到达 i,就能跳到 n。

这算是一部分,剩下就是一个dp,和记录路径的问题了。我们需要记录每个 i,是在那个 数组上 交换的,和交换之后的值。最终输出路径即可。

代码:

这里值得一提的是,我将dp这个数组略去了,因为我们最终只需要判断 1 是否能够跳到n。因为我们是逆序遍历的,所以最终的flag,就是1能不能跳到 n。

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

int main() {
    int t;
    cin >> t;

    while (t -- ) {
        int n;
        cin >> n;

        vector<vector<int>> op(3, vector<int>(n));
        for (int i = 0; i < 3; i ++ ) {
            for (int j = 0; j < n; j ++ ) {
                cin >> op[i][j];
            }
        }

        int flag;
        vector<pair<int, int>> minP(3), f(n + 1);
        for (int i = 0; i < 3; i ++ ) {
            minP[i] = {op[i][n - 1], n};
        }
        for (int i = n - 2; i >= 0; i -- ) {
            flag = false;
            for (int j = 0; j < 3; j ++ ) {
                if (op[j][i] > minP[j].first) {
                    f[i + 1] = {j, minP[j].second};
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                continue;
            }
            for (int j = 0; j < 3; j ++ ) {
                if (minP[j].first > op[j][i]) {
                    minP[j] = {op[j][i], i + 1};
                }
            }
        }

        string str = "QKJ";

        if (flag) {
            cout << "Yes" << endl;

            int now = 1;
            vector<pair<int, int>> ans;
            while (now < n) {
                ans.push_back(f[now]);
                now = f[now].second;
            }

            cout << ans.size() << endl;
            for (auto [x, y] : ans) {
                cout << str[x] << " " << y << endl;
            }
        }
        else {
            cout << "No" << endl;
        }
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值