AtCoder Beginner Contest 460 ABCDE

A - Mod While Positive

  • 预估难度:入门

题意

给定两个正整数 \(N\)\(M\)

只要 \(M\) 的值不为 \(0\),就不断重复执行以下操作:

  • \(M\) 的值替换为 \(N \bmod M\)

问该操作总共会执行多少次。

思路

由于 \(N \bmod M\) 的结果一定 \(\lt M\),因此 \(M\) 在过程中一定是单调递减的。

根据数据范围,可以根据题意直接模拟循环语句,并在过程中统计循环执行次数即可。

代码

void solve()
{
    int n, m;
    cin >> n >> m;
    
    int t = 0;
    while(m > 0)
    {
        m = n % m;
        t++;
    }
    
    cout << t;
}

B - Two Rings

  • 预估难度:普及-
  • 标签:计算几何

题意

\(xy\) 平面上有两个圆 \(C_1\)\(C_2\)。在这个问题中,“圆”指的是圆周(即圆的边缘轮廓)。
\(C_1\) 的圆心位于 \((X_1, Y_1)\),半径为 \(R_1\)
\(C_2\) 的圆心位于 \((X_2, Y_2)\),半径为 \(R_2\)

请判断圆 \(C_1\)\(C_2\) 是否有公共点。换句话说,请判断是否存在至少一个点,它到 \((X_1, Y_1)\) 的距离恰好是 \(R_1\),并且到 \((X_2, Y_2)\) 的距离恰好是 \(R_2\)

思路

两个圆在平面内的位置关系有五种,分别为外离、外切、相交、内切、内含,其中仅外离内含两种情况不存在公共点。

当两圆外离时,连接两圆圆心作一条辅助线,可以发现此时 两圆圆心距 \(\gt\) 两圆半径和

当两圆内含时,同样通过连接两圆圆心作一条辅助线,并将辅助线延伸到外部圆形的圆周上,可以发现此时 两圆圆心距 \(+\) 内部圆形半径 \(\lt\) 外部圆形半径,即 两圆圆心距 \(\lt\) 两圆半径差

特判以上两种情况即可。但需注意坐标数据范围较大,如果直接计算圆心距 \(\sqrt{\Delta x^2 + \Delta y^2}\) 后再判断可能会在计算平方根时出现浮点数误差,因此建议不取这一步的平方根,而在判断时直接用平方后的数进行处理即可。

代码

void solve() // 单组数据
{
    long long xa, ya, ra, xb, yb, rb;
    cin >> xa >> ya >> ra >> xb >> yb >> rb;
    
    long long dx = xa - xb, dy = ya - yb;
    long long dist2 = dx * dx + dy * dy; // 圆心距的平方
    
    // 圆心距 > 半径和 || 圆心距 < 半径差
    if(dist2 > (ra + rb) * (ra + rb) || dist2 < (ra - rb) * (ra - rb))
        cout << "No\n";
    else
        cout << "Yes\n";
}

C - Sushi

  • 预估难度:普及-
  • 标签:排序、贪心、双指针

题意

你有 \(N\) 个寿司饭团和 \(M\) 个配料,作为制作寿司的食材。

\(i\) 个饭团的重量是 \(A_i\),第 \(j\) 个配料的重量是 \(B_j\)

你将通过搭配饭团和配料来制作寿司。

要制作一个寿司,你需要将一个饭团和一个配料组合在一起。
这里有一个限制条件:配料的重量不能超过饭团重量的两倍(即 \(B_j \le 2 \times A_i\))。
此外,同一个饭团或配料不能被用于多个寿司中(每个只能用一次)。

请计算最多能制作多少个寿司。

思路

先对所有饭团与所有配料按重量从小到大排序。

接下来可以从饭团角度出发选择配料,或者从配料角度出发选择饭团均可。下文以饭团角度进行讨论。

对于重量越大的饭团,其对应的配料重量条件就会越宽松,因此接下来先按重量从大到小看一遍所有的饭团。

由于能与饭团搭配的配料重量不能超过饭团重量的两倍,因此对于配料而言,重量越大有可能就越不好搭配。所有我们应该尽可能优先地用掉重量更大的配料。

所以对于重量为 \(A_i\) 的饭团,我们可以优先选择重量不超过 \(2A_i\) 的所有配料当中重量最大的配料来与其搭配。

而当搭配完重量较大的饭团后,随着剩余饭团重量逐渐变小,能够与其搭配的配料重量也会随之变小,因此可以借助双指针来维护目前还能够选择的配料中重量最大的配料

单组数据时间复杂度 \(O(N\log N + M\log M)\)

代码

int n, m;
int a[200005], b[200005];

void solve()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    for(int i = 1; i <= m; i++)
        cin >> b[i];
    
    sort(a + 1, a + n + 1);
    sort(b + 1, b + m + 1);
    
    int ans = 0;
    
    // 从大到小看一遍所有饭团
    // j 维护此时还没用过的配料中 重量最大的配料
    for(int i = n, j = m; i >= 1; i--)
    {
        // 只要此时重量最大的配料没法用,就往前看重量偏小的
        while(j >= 1 && b[j] > 2 * a[i])
            j--;
        // 如果越界,说明配料已经全部用完
        if(j < 1)
            break;
        else
        {
            ans++;
            j--;
        }
    }
    
    cout << ans;
}

D - Repeatedly Repainting

  • 预估难度:普及/提高-
  • 标签:宽度优先搜索

题意

有一个 \(H\)\(W\) 列的网格图。

每个单元格被染成白色或黑色。字符 . 表示白色,字符 # 表示黑色。

你将执行以下操作 \(10^{100}\) 次:

  • 每次操作,对所有单元格同时应用以下规则:
    • 在操作前为白色的单元格,当且仅当它周围存在至少一个相邻的黑色单元格时,会变成黑色。这里的“相邻”是指两个单元格 \((x, y)\)\((x', y')\) 八相邻(即 \(\max(|x-x'|, |y-y'|) = 1\),包含上下左右及四个对角线方向)。
    • 在操作前为黑色的单元格,会变成白色。

输出所有操作结束后,每个单元格的最终颜色。

思路

如果在过程中存在一对相邻的格子是一黑一白,很明显接下来每次操作,白的会变黑、黑的会变白,再进行一次操作后又会变回来。也就是说,这样的两个相邻格子在接下来的操作中颜色会相互交替。

因此,对于这种格子,我们只需要关注每个格子第一次变为黑色的时间的奇偶性,其后根据奇偶性来判断在第 \(10^{100}\) 次操作完成后是黑格子还是白格子即可。

至于每个格子第一次变为黑色格子的时间,这和每个格子移动到最近的黑色点上的步数是相同的。因此这一部分可以直接借助多源点的宽度优先搜索来实现。

但本题存在一种特殊情况,例如样例二,当有某个黑色点的周围也全是黑色点时,此时该点在两次操作后并不能重新变回黑色点。但由于在操作过程中,不可能出现将某个 \(3 \times 3\) 的格子全部变为黑色点的情况,因此这种情况只可能出现在地图刚输入的时候。解决方法也很简单,我们可以先手动模拟一遍第一次操作,去除这种情况,然后再进行宽搜求解。

时间复杂度 \(O(N\times M)\)

代码

const int INF = 0x3f3f3f3f;
const int dx[8] = {0, 1, 0, -1, 1, 1, -1, -1}, dy[8] = {1, 0, -1, 0, 1, -1, 1, -1};

int n, m;

void solve()
{
    cin >> n >> m;
    
    vector<vector<char>> mp = vector<vector<char>>(n + 1, vector<char>(m + 1));
    vector<vector<char>> mp2 = vector<vector<char>>(n + 1, vector<char>(m + 1, '.'));
    
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            cin >> mp[i][j];
    
    // 先模拟移动一次,去除周围都是 # 的黑色格子
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            if(mp[i][j] == '#')
                for(int k = 0; k < 8; k++)
                {
                    int px = i + dx[k], py = j + dy[k];
                    if(px < 1 || py < 1 || px > n || py > m)
                        continue;
                    if(mp[px][py] == '.') // 原图中只要该点是白色的,就会被染黑
                        mp2[px][py] = '#';
                }
    mp = mp2;
    
    vector<vector<int>> dis = vector<vector<int>>(n + 1, vector<int>(m + 1, INF));
    
    queue<pii> q;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            if(mp[i][j] == '#')
            {
                q.push(pii(i, j));
                dis[i][j] = 0;
            }
    
    while(!q.empty())
    {
        pii p = q.front();
        q.pop();
        int x = p.first, y = p.second;
        for(int i = 0; i < 8; i++)
        {
            int px = x + dx[i], py = y + dy[i];
            if(px < 1 || py < 1 || px > n || py > m)
                continue;
            if(dis[px][py] == INF)
            {
                dis[px][py] = dis[x][y] + 1;
                q.push(pii(px, py));
            }
        }
    }
    
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            // 如果这个点第一次变为黑色格子的时间是偶数
            // 由于我们先前手动移动了一次,因此实际时间是奇数
            // 又由于 10^100 是偶数,因此此时该格子应当是白色格子
            // 或者如果地图像样例二一样,移动一次后不存在黑色格子,也直接输出白色
            if(dis[i][j] % 2 == 0 || dis[i][j] == INF)
                cout << '.';
            else
                cout << '#';
        }
        cout << "\n";
    }
}

E - x + y ≡ x + y

  • 预估难度:普及+/提高
  • 标签:数论、同余方程、枚举

题意

对于正整数 \(a\)\(b\),定义 \(\mathrm{concat}(a, b)\) 为将 \(a\)\(b\) 前后拼接所形成的整数。
例如,如果 \(a = 123\)\(b = 45\),那么 \(\mathrm{concat}(a, b) = 12345\)

给定两个正整数 \(N\)\(M\)
请找出满足以下条件的正整数对 \((x, y)\) 的数量(结果对 \(998244353\) 取模):

  • \(x\)\(y\) 均不超过 \(N\)
  • 满足同余式 \(\mathrm{concat}(x, y) \equiv x + y \pmod{M}\)

思路

\(|y|\) 表示 \(y\) 的数位个数(即数字长度),那么 \(\mathrm{concat}(x, y)\) 也可以表示为 \(x \times 10^{|y|} + y\)。得:

\[\begin{aligned} \mathrm{concat}(x, y) &\equiv x + y \pmod{M} \\ x \times 10^{|y|} + y &\equiv x + y \pmod{M} \\ x \times 10^{|y|} &\equiv x \pmod{M} \\ x \times 10^{|y|} - x &\equiv 0 \pmod{M} \\ x \times (10^{|y|} - 1) &\equiv 0 \pmod{M} \end{aligned} \]

可以发现该式与 \(y\) 的实际值无关,仅与其长度有关,因此可以借助枚举法先确定 \(y\) 的长度 \(|y|\)

在已知公式中的 \(10^{|y|}-1\) 以及 \(M\) 这两个数值后,我们需要找出 \(1 \sim N\) 范围内有多少个 \(x\) 符合上式。

考虑约分同余式,记 \(g = \gcd(10^{|y|} - 1, M)\),即两者最大公因数,那么上式可以继续写作:

\[x \times \frac{10^{|y|} - 1}{g} \equiv 0 \pmod{\frac{M}{g}} \]

由于约分后 \(\frac{10^{|y|} - 1}{g}\)\(\frac{M}{g}\) 互质,因此为了让 \(x \times \frac{10^{|y|} - 1}{g}\) 成为 \(\frac{M}{g}\) 的倍数,我们只能够选择让 \(x\) 成为 \(\frac{M}{g}\) 的倍数。

那么统计 \(x\) 数量的问题便转化成了求 \(1 \sim N\) 以内的 \(\frac{M}{g}\) 的倍数数量,答案即 \(\lfloor\dfrac{N}{\frac{M}{g}}\rfloor\) 个。

对于每个符合条件的 \(x\),都能与符合条件的 \(y\) 组成一对符合题意的正整数对。\(1 \sim N\) 以内的长度为 \(|y|\) 的数字 \(y\) 的数量可以直接通过计算,或是在枚举长度时用符合条件的区间顺便求出。

由于 \(N \le 10^{18}\),示例代码的写法可能会出现到达 \(10^{19}\) 的情况,因此采用了 unsigned long long 类型进行计算。

单组数据时间复杂度 \(O(\log^2 \max(N,M))\)

代码

typedef unsigned long long ull;
const ull mod = 998244353;

ull gcd(ull a, ull b)
{
    if(b == 0)
        return a;
    return gcd(b, a % b);
}

void solve() // 单组数据
{
    ull n, m;
    cin >> n >> m;
    
    ull ans = 0;
    ull l = 1, r = 9;
    // 假设满足条件的 y 的长度为 w,枚举该长度
    // 记 l, r 表示长度为 w 的整数的左右边界
    // l 也可记作 10 的 w-1 次方
    // r 也可记作 10 的 w 次方 -1
    while(l <= n)
    {
        ull cntY = min(r, n) - l + 1; // n 以内且位数为 w 的正整数个数
        cntY %= mod;
        
        ull g = gcd(r, m); // 10^w - 1 和 m 的最大公因数
        ull cntX = n / (m / g);
        cntX %= mod;
        
        ans = (ans + cntX * cntY) % mod;
        
        l = l * 10;
        r = r * 10 + 9;
    }
    
    cout << ans << "\n";
}
posted @ 2026-05-30 22:09  StelaYuri  阅读(71)  评论(0)    收藏  举报