Educational Codeforces Round 118 (Rated for Div. 2)

本文讨论了在查询限制条件下,如何通过巧妙地调整乘法运算和选择合适的比较尺度,避免溢出,快速比较两个大数的大小。方法涉及规模缩小、循环操作和条件判断,适用于长比较和特定数值运算问题。

A. Long Comparison

题意

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

给定 x1x_1x1p1p_1p1x2x_2x2p2p_2p2,问 x1×10p1x_1 \times 10^{p_1}x1×10p1x2×10p2x_2 \times 10^{p_2}x2×10p2 之间的大小关系。

分析

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

自然我们即就可以想到,我们可以让两边同除一个 10s10^s10s,以降低规模,有:x1×10p1/10s=x2×10p2/10sx1×10p1−s=x2×10p2−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⁡(p1,p2)s = \min(p_1, p_2)s=min(p1,p2) 即可使其中一个 p1−sp_1 - sp1s 或者 p2−sp_2 - sp2s 变为零。

不妨假设 p1p_1p1p1p_1p1p2p_2p2 两者中的最小值,也就是说 s=p1=min⁡(p1,p2)s = p_1 = \min(p_1, p_2)s=p1=min(p1,p2),那么我们就只需要比较 x1x_1x1x2×10p2−p1x_2 \times 10^{p_2 - p_1}x2×10p2p1。令 p2←p2−p1p_2 \gets p_2 - p_1p2p2p1

那么我们可以不断循环,令 p2←p2−1p_2 \gets p_2 - 1p2p21x2←10x2x_2 \gets 10 x_2x210x2,如果 p2p_2p2000 或者 x2x_2x2 已经大于 x1x_1x1,那么我们就退出循环,可以证明这样是不会超过范围的。最后输出答案即可。

参考代码

#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≤104t \le 10^4t104 次查询。

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

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

  1. x≠yx \ne yx=y
  2. xxxyyy 都出现在 aaa 中。
  3. x mod yx \bmod yxmody 不出现在 aaa 中。

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

输出一个可行的解。

分析

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

那么自然对于任意的 iii,有 ai mod m<ma_i \bmod m < maimodm<m,而 ai mod ma_i \bmod maimodm 自然不在 aaa 中(不然 mmm 就不是最小值了)。

那么我们只需要输出 ⌊n2⌋\lfloor{n \over 2}\rfloor2n⟨ai≠m,m⟩\langle a_i \ne m, m\rangleai=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≤1018h \le 10^{18}h1018

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

求能打败龙的最小的 kkk

分析

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

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

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

参考代码

#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

题意

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

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

分析

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

a.png

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

  1. 其中 P1 表示情况一。它的每一个 xix_ixi 均满足 xi=xi−1x_i = x_{i - 1}xi=xi1 或者 xi=xi−1+1x_i = x_{i - 1} +1xi=xi1+1。每一个 P1,它都是从 P1 所转移过来的(假设旧的 P1 的最后的坐标是 i−1i - 1i1,那么能且仅能在旧的 P1 后面添加 xi=xi−1x_i = x_{i - 1}xi=xi1 或者 xi=xi−1+1x_i = x{i - 1} + 1xi=xi1+1 才能得到新的 P1)。
  2. 其中 P2 表示情况二。我们注意到,它的 xi=MEX(x1,x2,…,xi)+1x_i = \mathrm{MEX}(x_1, x_2, \ldots, x_i) + 1xi=MEX(x1,x2,,xi)+1。每一个 P2,都是从 P1、P2 或者 P3 转移过来的。
  3. 其中 P3 表示情况三。我们注意到,它的 xi=MEX(x1,x2,…,xi)−1x_i = \mathrm{MEX}(x_1, x_2, \ldots, x_i) - 1xi=MEX(x1,x2,,xi)1,不过和 P1 不同的是,这些序列的最高值是 MEX(x1,x2,…,xi)+1\mathrm{MEX}(x_1, x_2, \ldots, x_i) + 1MEX(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≤106n \le 10^6n106m≤106m \le 10^6m106 列的二维地图。

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

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

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

题解

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

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

  1. 我们从一个未处理的、并且证实行得通的格子构成的队列中弹出来一个。命名为 sss
  2. 我们遍历 sss 的四周。我们只遍历空格子、并且未被证实行得通的格子。假设当前遍历到了 s′s's,那么自然 s′s's 的行得通的方向又多了一个(到 sss)。如果行不通的方向小于等于 111 的话,那么 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值