Educational Codeforces Round 118 (Rated for Div. 2)

A. Long Comparison

题意

t ≤ 1 0 4 t \le 10^4 t104 次查询。

给定 x 1 x_1 x1 p 1 p_1 p1 x 2 x_2 x2 p 2 p_2 p2,问 x 1 × 1 0 p 1 x_1 \times 10^{p_1} x1×10p1 x 2 × 1 0 p 2 x_2 \times 10^{p_2} x2×10p2 之间的大小关系。

分析

如果只是单纯地进行运算后比较,但是运算可能会导致溢出。

自然我们即就可以想到,我们可以让两边同除一个 1 0 s 10^s 10s,以降低规模,有: x 1 × 1 0 p 1 / 1 0 s = x 2 × 1 0 p 2 / 1 0 s x 1 × 1 0 p 1 − s = x 2 × 1 0 p 2 − s \begin{aligned} x_1 \times 10^{p_1} / 10^s & = x_2 \times 10^{p_2} / 10^s \\ x_1 \times 10^{p_1 - s} & = x_2 \times 10^{p_2 - s} \\ \end{aligned} x1×10p1/10sx1×10p1s=x2×10p2/10s=x2×10p2s

s = min ⁡ ( p 1 , p 2 ) s = \min(p_1, p_2) s=min(p1,p2) 即可使其中一个 p 1 − s p_1 - s p1s 或者 p 2 − s p_2 - s p2s 变为零。

不妨假设 p 1 p_1 p1 p 1 p_1 p1 p 2 p_2 p2 两者中的最小值,也就是说 s = p 1 = min ⁡ ( p 1 , p 2 ) s = p_1 = \min(p_1, p_2) s=p1=min(p1,p2),那么我们就只需要比较 x 1 x_1 x1 x 2 × 1 0 p 2 − p 1 x_2 \times 10^{p_2 - p_1} x2×10p2p1。令 p 2 ← p 2 − p 1 p_2 \gets p_2 - p_1 p2p2p1

那么我们可以不断循环,令 p 2 ← p 2 − 1 p_2 \gets p_2 - 1 p2p21 x 2 ← 10 x 2 x_2 \gets 10 x_2 x210x2,如果 p 2 p_2 p2 0 0 0 或者 x 2 x_2 x2 已经大于 x 1 x_1 x1,那么我们就退出循环,可以证明这样是不会超过范围的。最后输出答案即可。

参考代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main()
{
    int t;
    scanf("%d", &t);
    while (t--) {
        // 输入。
        int x1, p1;
        scanf("%d%d", &x1, &p1);
        int x2, p2;
        scanf("%d%d", &x2, &p2);
        int m = min(p1, p2);

        // 初次降低规模。
        p1 -= m;
        p2 -= m;

        // 通过交换以令 p1 为 0。
        int flg = true;
        if (p1) {
            swap(x1, x2);
            swap(p1, p2);
            flg = false;
        }

        // 循环直到 p2 == 0 或者已经 x2 > x1。
        while (x2 <= x1 && p2) {
            p2--;
            x2 *= 10;
        }

        if (x2 > x1) {
            // 无论 p2 是什么值,x1 < x2 * 10^p2
            printf(flg ? "<" : ">");
        } else if (x2 < x1) {
            // 这说明 p2 == 0 一定成立,故 x1 > x2 <=> x1 > x2 * 10^p2
            printf(flg ? ">" : "<");
        } else {
            // x1 == x2,同时 p2 == 0。
            printf("=");
        }
        printf("\n");
    }

    return 0;
}

B. Absent Remainder

题意

一共有 t ≤ 1 0 4 t \le 10^4 t104 次查询。

给定一个序列, a = { a 1 , a 2 , … , a n } a = \{a_1, a_2, \ldots, a_n\} a={a1,a2,,an},其中这些元素它们两两不同,一共有 n ≤ 2 × 1 0 5 n \le 2 \times 10^5 n2×105 个(所有查询的 n n n 的和 ∑ n \sum n n 也小于等于 1 0 5 10^5 105)元素,对于任意的 i i i 1 ≤ a i ≤ 1 0 6 1 \le a_i \le 10^6 1ai106

我们需要得到两两不同的 ⌊ n 2 ⌋ \lfloor{n \over 2}\rfloor 2n 对元素,其中每一对 ⟨ x , y ⟩ \langle x, y\rangle x,y 需要满足:

  1. x ≠ y x \ne y x=y
  2. x x x y y y 都出现在 a a a 中。
  3. x   m o d   y x \bmod y xmody 不出现在 a a a 中。

注意,一些 x x x y y y 可以出现在多个对中,只需要 ⟨ x , y ⟩ \langle x, y\rangle x,y 两两不同即可。

输出一个可行的解。

分析

这是一个构造题,所以这需要我们构造答案。我们不妨令最小值为 m = min ⁡ ( a 1 , a 2 , … , a n ) m = \min(a_1, a_2, \ldots, a_n) m=min(a1,a2,,an)

那么自然对于任意的 i i i,有 a i   m o d   m < m a_i \bmod m < m aimodm<m,而 a i   m o d   m a_i \bmod m aimodm 自然不在 a a a 中(不然 m m m 就不是最小值了)。

那么我们只需要输出 ⌊ n 2 ⌋ \lfloor{n \over 2}\rfloor 2n ⟨ a i ≠ m , m ⟩ \langle a_i \ne m, m\rangle ai=m,m 即可。

参考代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main()
{
    int t;
    scanf("%d", &t);
    while (t--) {
        // 输入。并得到最小值 m。
        int n;
        scanf("%d", &n);

        constexpr int LEN = 2e5 + 16;
        static int A[LEN];
        int m = 0x3f3f3f3f;
        for (int i = 0; i < n; i++) {
            scanf("%d", &A[i]);
            m = min(m, A[i]);
        }

        // 输出 n / 2 个。并且要注意 a_i != m,所以我们用另外一个变量,pt 来维
        // 护我们遍历到的元素。
        for (int i = 0, pt = 0; i < n / 2; i++) {
            while (A[pt] == m)
                pt++;
            printf("%d %d\n", A[pt], m);
            pt++;
        }
    }
    return 0;
}

C. Poisoned Dagger

题意

目前,我们在攻击一条龙,它的 HP 为 h ≤ 1 0 18 h \le 10^{18} h1018

一共有 1 ≤ n ≤ 100 1 \le n \le 100 1n100 次攻击,第 i i i 次攻击发生在 a i a_i ai 的时刻。它会造成持续未知的 k k k 秒的 debuff,而 debuff,会导致每一秒都会减掉龙的 1 1 1 点生命值。如果它的上一次攻击造成的 debuff 还没消失,而下一次攻击就已经到来的话,debuff 也 不会 叠加,而是它先清空掉前一次的 debuff,再应用下一次攻击的 debuff。

求能打败龙的最小的 k k k

分析

这道题,我们发现 k k k 最小可能是 1 1 1,最大可能是 1 0 18 10^{18} 1018。如此巨大的范围,自然是无法遍历的。

我们发现, k k k 越大,造成的伤害越高,也就是说从最小的有效 k k k 开始,之后比它大全是有效的,之前比它小的全是无效的。

那么二分答案即可。详情请参考参考代码。

参考代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main()
{
    int t;
    scanf("%d", &t);
    while (t--) {
        int n;
        ll h;
        scanf("%d%lld", &n, &h);

        ll l = 1, r = 1e18;
        static ll A[110];
        for (int i = 0; i < n; i++) {
            scanf("%lld", &A[i]);
        }
        // 末尾放一个无穷大的以方便处理。
        A[n] = 0x3f3f3f3f3f3f3f3fLL;

        // 二分。
        while (l < r) {
            ll mid = (l + r) / 2;

            // 根据 mid,计算能造成的伤害 res。O(n) 的复杂度。
            ll res = 0;
            for (int i = 0; i < n; i++) {
                res += min(mid, A[i + 1] - A[i]);
            }

            // 缩小二分的范围。
            if (res < h) {
                l = mid + 1;
            } else {
                r = mid;
            }
        }
        printf("%lld\n", l);
    }
    return 0;
}

D. MEX Sequences

题意

我们定义一个序列, x 1 , x 2 , … , x k x_1, x_2, \ldots, x_k x1,x2,,xk,对于 1 ≤ i ≤ k 1 \le i \le k 1ik,如果任意的 i i i,表达式 ∣ x i − M E X ( x 1 , x 2 , … , x i ) ∣ ≤ 1 |x_i - \mathrm{MEX}(x_1, x_2, \ldots, x_i)| \le 1 xiMEX(x1,x2,,xi)1 恒成立,那么我们说这个序列是好的。

我们现在需要知道,对于一个给定的序列,有多少子序列是好的。

分析

首先我们必须得分析表达式 ∣ x i − M E X ( x 1 , x 2 , … , x i ) ∣ ≤ 1 |x_i - \mathrm{MEX}(x_1, x_2, \ldots, x_i)| \le 1 xiMEX(x1,x2,,xi)1 的性质。这说明 x i x_i xi M E X ( x 1 , x 2 , … , x i ) \mathrm{MEX}(x_1, x_2, \ldots, x_i) MEX(x1,x2,,xi) 最多只相差一。

a.png

如上所示(其中红色的球代表了序列,而绿色的球表示序列对应的 M E X \mathrm{MEX} MEX 值),它一共就只有三种情况:

  1. 其中 P1 表示情况一。它的每一个 x i x_i xi 均满足 x i = x i − 1 x_i = x_{i - 1} xi=xi1 或者 x i = x i − 1 + 1 x_i = x_{i - 1} +1 xi=xi1+1。每一个 P1,它都是从 P1 所转移过来的(假设旧的 P1 的最后的坐标是 i − 1 i - 1 i1,那么能且仅能在旧的 P1 后面添加 x i = x i − 1 x_i = x_{i - 1} xi=xi1 或者 x i = x i − 1 + 1 x_i = x{i - 1} + 1 xi=xi1+1 才能得到新的 P1)。
  2. 其中 P2 表示情况二。我们注意到,它的 x i = M E X ( x 1 , x 2 , … , x i ) + 1 x_i = \mathrm{MEX}(x_1, x_2, \ldots, x_i) + 1 xi=MEX(x1,x2,,xi)+1。每一个 P2,都是从 P1、P2 或者 P3 转移过来的。
  3. 其中 P3 表示情况三。我们注意到,它的 x i = M E X ( x 1 , x 2 , … , x i ) − 1 x_i = \mathrm{MEX}(x_1, x_2, \ldots, x_i) - 1 xi=MEX(x1,x2,,xi)1,不过和 P1 不同的是,这些序列的最高值是 M E X ( x 1 , x 2 , … , x i ) + 1 \mathrm{MEX}(x_1, x_2, \ldots, x_i) + 1 MEX(x1,x2,,xi)+1。每一个 P3,都是从 P2 或者 P3 转移过来的。

那么我们自然可以列出转移方程,并根据转移方程来列出三种情况的好的子序列数即可。
更多细节请参考参考代码。

参考代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int MOD = 998244353;

constexpr int LEN = 5e5 + 16;

int main()
{
    int t;
    scanf("%d", &t);
    while (t--) {
        // 输入。
        int n;
        scanf("%d", &n);

        static int A[LEN];
        for (int i = 0; i < n; i++) {
            scanf("%d", &A[i]);
        }

        // 定义 dp 并全部初始化为 0。
        static int dp[3][LEN];
        memset(dp[0], 0, sizeof(int) * (n + 5));
        memset(dp[1], 0, sizeof(int) * (n + 5));
        memset(dp[2], 0, sizeof(int) * (n + 5));

        for (int i = 0; i < n; i++) {
            int ele = A[i];

            // 状态 P1(下标为 0)由 P1 转移过来。
            dp[0][ele] += (dp[0][ele] + (ele >= 1 ? dp[0][ele - 1] : 0)) % MOD;
            // 状态 P2(下标为 1)由 P1、P2、P3 转移过来。)
            dp[1][ele] += (dp[1][ele] + (ele >= 2 ? (dp[0][ele - 2] + dp[2][ele - 2]) % MOD : 0)) % MOD;
            // 状态 P3(下标为 2)由 P2、P3 转移过来。)
            dp[2][ele] += (dp[2][ele] + dp[1][ele + 2]) % MOD;

            dp[0][ele] %= MOD;
            dp[1][ele] %= MOD;
            dp[2][ele] %= MOD;

            // 如果 ele == 0,它自己就可以单独构成 P1。
            if (ele == 0) {
                dp[0][ele] += 1;
            }

            // 如果 ele == 1,它自己就可以单独构成 P2。
            if (ele == 1) {
                dp[1][ele] += 1;
            }
        }

        // 累加即为解。
        ll res = 0;
        for (int i = 0; i <= n; i++) {
            res = ((res + dp[0][i]) % MOD + dp[1][i]) % MOD + dp[2][i];
            res %= MOD;
        }
        printf("%lld\n", res);
    }
    return 0;
}

E. Crazy Robot

题意

给定 n ≤ 1 0 6 n \le 10^6 n106 m ≤ 1 0 6 m \le 10^6 m106 列的二维地图。

地图上有且仅有一个单位格表示实验室,此外有若干个障碍物和若干个空地。

对于每一个空地,判断我们能否肯定通过操作机器人使其返回到实验室。其中操作如下:我们可以给出一个方向,然后机器人会在剩下的三个方向中选择一个方向,并前进一。如果三个方向都有障碍,它才不会行动。

对于每一个空地,我们都判断,并利用判断的结果为空地打上标记,并输出标记后的地图。

题解

我们可以观察到,对于如果一个单元格能到达的格子除了 至多一个 方向行不通的以外、其他方向都行得通的话,那么这个单元格也就是行得通的(实际中,我们只需要堵住那个行不通的即可)。反之依然。

那么我们可以使用 BFS 来求解。在 BFS 中,我们在已经证实行得通的基础上来扩展答案:

  1. 我们从一个未处理的、并且证实行得通的格子构成的队列中弹出来一个。命名为 s s s
  2. 我们遍历 s s s 的四周。我们只遍历空格子、并且未被证实行得通的格子。假设当前遍历到了 s ′ s' s,那么自然 s ′ s' s 的行得通的方向又多了一个(到 s s s)。如果行不通的方向小于等于 1 1 1 的话,那么 s ′ s' s 也是一个行得通的点了,不过它还没有处理,所以我们把它放在队列中。
  3. 处理,直到没有未处理的点。

因为行得通的点都是连通的,所以一个点如果行得通的话,那么它必然被另外的行得通的点或者实验室所 BFS 到!

参考代码

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int t;
    scanf("%d", &t);
    while (t--) {
        // 输入。顺便得到图书馆的坐标 (lx, ly)。
        int n, m;
        scanf("%d%d", &n, &m);
        constexpr int LEN = 1e6 + 16;
        static char S[LEN];
        vector<string> MP;
        int lx, ly;
        for (int i = 0; i < n; i++) {
            scanf("%s", S);
            for (int j = 0; j < m; j++) {
                if (S[j] == 'L') {
                    lx = i, ly = j;
                }
            }
            MP.push_back(S);
        }

        // 方向。
        static const int dx[] = { 0, 1, 0, -1 };
        static const int dy[] = { 1, 0, -1, 0 };

        // D 用来储存 (i, j) 对应的方向个数。
        vector<vector<int>> D;
        for (int i = 0; i < n; i++) {
            D.push_back(vector<int>(m, 0));
            for (int j = 0; j < m; j++) {
                for (int k = 0; k < 4; k++) {
                    int xx = i + dx[k];
                    int yy = j + dy[k];
                    if (xx < 0 || xx >= n)
                        continue;
                    if (yy < 0 || yy >= m)
                        continue;
                    if (MP[xx][yy] == '#')
                        continue;
                    D[i][j]++;
                }
            }
        }

        // BFS。第一个是实验室。
        queue<int> qx, qy;
        qx.push(lx), qy.push(ly);
        while (!qx.empty()) {
            int x = qx.front();
            qx.pop();
            int y = qy.front();
            qy.pop();
            for (int i = 0; i < 4; i++) {
                int xx = x + dx[i];
                int yy = y + dy[i];
                if (xx < 0 || xx >= n)
                    continue;
                if (yy < 0 || yy >= m)
                    continue;
                if (MP[xx][yy] != '.')
                    continue;
                --D[xx][yy];
                // 说明有小于等于一个方向能走到不可及的地方。
                // 那么标记行得通,并 push 到队列中。
                if (D[xx][yy] <= 1) {
                    MP[xx][yy] = '+';
                    qx.push(xx);
                    qy.push(yy);
                }
            }
        }

        // 输出。
        for (int i = 0; i < n; i++) {
            printf("%s\n", MP[i].c_str());
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值