AtCoder Beginner Contest 472 ABCDE

A - A

  • 预估难度:入门
  • 标签:字符串

题意

给定一个由大写英文字母组成的字符串 \(S\)

请将 \(S\) 中除 A 以外的所有字符替换为 .,并输出替换后得到的字符串。

数据范围

  • \(1 \le |S| \le 100\)
  • \(S\) 仅由大写英文字母组成

代码

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

int main()
{
    string s;
    cin >> s;
    
    for(char c : s)
    {
        if(c == 'A')
            cout << 'A';
        else
            cout << '.';
    }
    
    return 0;
}

B - Break a Stick

  • 预估难度:入门
  • 标签:枚举

题意

有一根木棍。这根木棍上有 \(N-1\) 个凹槽,将其分成了 \(N\) 段。

从木棍的一端开始,这 \(N\) 段的长度依次为 \(L_1, L_2, \dots, L_N\)

如果选择一个刻度将木棍折断,从而得到两根木棍,请找出折断后得到的两根木棍长度之差的绝对值的最小可能值。

注意:刻度的宽度可以忽略不计,折断后得到的每根木棍的长度等于其所包含的各段长度之和。

数据范围

  • \(2 \leq N \leq 100\)
  • \(1 \leq L_i \leq 10^5\)

思路

题意也就是将数组分为 \([1, i], [i+1, N]\) 两段,且让这两段总和的差值最小。

直接枚举分界点 \(i\),然后循环嵌套分别求解 \([1, i]\)\([i+1, N]\) 两段各自的总和,再计算差值绝对值取最小即可,时间复杂度 \(O(N^2)\),足够通过。

也可以结合前缀和思想,从左往右枚举分界点 \(i\) 时顺便直接维护 \([1, i]\) 的总和,那么后半段 \([i + 1, N]\) 的总和可以直接借助整个数组的总和减去前半段来得到,时间复杂度 \(O(N)\)

代码

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

int n, l[105];

int main()
{
    cin >> n;
    
    int sum = 0; // 总和
    for(int i = 1; i <= n; i++)
    {
        cin >> l[i];
        sum += l[i];
    }
    
    int pre = 0; // 1 ~ i 的总和
    int ans = 1e9; // 答案
    
    for(int i = 1; i <= n; i++)
    {
        pre += l[i];
        // 后半段总和即 (sum - pre)
        ans = min(ans, abs((sum - pre) - pre));
    }
    cout << ans;
    
    return 0;
}

C - On a Diet

  • 预估难度:普及-
  • 标签:计数思想、模拟

题意

高桥正在父母家进行为期 \(N\) 天的探亲之旅。

在父母家里,他每天都有不同的零食吃。第 \(i\) 天的零食热量为 \(A_i\)

为了管理健康,他会保证连续 \(M\) 天内吃的零食总热量不超过 \(K\)

具体来说,按照 \(i=1,2,\dots,N\) 的顺序,他根据以下规则决定是否吃第 \(i\) 天的零食:

  • 假设当前为第 \(i\) 天,如果从第 \(\max(i-M+1,1)\) 天到第 \(i-1\)已经吃过的零食总热量再加上第 \(i\) 天的零食热量不会超过 \(K\) 时,那么他才会选择在第 \(i\) 天吃零食。

对于每个 \(i=1,2,\dots,N\),请判断高桥在第 \(i\) 天是否吃了零食。

数据范围

  • \(1 \leq M \leq N \leq 2 \times 10^5\)
  • \(1 \leq K \leq 10^{15}\)
  • \(1 \leq A_i \leq 10^9\)

思路

按顺序枚举日期 \(i\),那么接下来的主要目的就是要快速求出第 \(\max(i-M+1, 1) \sim i-1\)\(M-1\) 天内已经吃了多少热量的零食。

记最近 \(M\) 天(先不包含当前这一天)吃的零食总热量为 sum,然后通过判断 sum + A[i] 是否超过 \(K\) 来决定是否要在第 \(i\) 天吃零食。这是一个基础滑动窗口的变型,只需要动态维护变量 sum 的值即可。

先假设 \(i-M \ge 1\),那么当枚举到第 \(i-1\) 天时,sum 维护的就是 \(i-M \sim i-1\) 这些天所吃的零食热量总和。当枚举的日期变为第 \(i\) 天,sum 需要改为维护第 \(i-M+1 \sim i\) 这些天所吃的零食热量总和。比较前后两段区间可以发现,这里只需要将第 \(i-M\) 天对 sum 的影响去除,即可继续判断第 \(i\) 天是否吃零食。

注意如果 \(i-M \lt 1\) 或是第 \(i-M\) 天没有选择吃零食,就不要去把这天的影响去除。这步判断可以通过维护一个计数数组 eat[i] 来实现,用于表示第 \(i\) 天是否选择吃零食。

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

代码

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

int n, m, a[200005];
ll k;

bool eat[200005]; // 标记每一天是否吃零食

int main()
{
    cin >> n >> m >> k;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    
    ll sum = 0; // 最近 m 天内吃的总热量
    for(int i = 1; i <= n; i++)
    {
        // “最近 m 天”的范围为 i-m+1 ~ i,因此需要排除第 i-m 天的影响
        if(i - m >= 1 && eat[i - m]) // 如果第 i-m 天有吃零食
            sum -= a[i - m];
        
        if(sum + a[i] <= k) // 如果加上第 i 天的零食后,总热量依旧不超过 k,才会吃
        {
            eat[i] = true;
            sum += a[i];
            cout << "Yes\n";
        }
        else
            cout << "No\n";
    }
    
    return 0;
}

D - Bomber Mad

  • 预估难度:普及
  • 标签:计数思想、宽度优先搜索

题意

有一个 \(H\)\(W\) 列的网格。每个格子要么是空格子,要么是炸弹格子。我们用 \((i,j)\) 表示从上往下数第 \(i\) 行、从左往右数第 \(j\) 列的格子。网格由 \(H\) 个长度为 \(W\) 的字符串 \(S_1, S_2, \dots, S_H\) 给出:如果 \(S_i\) 的第 \(j\) 个字符是 .,则 \((i,j)\) 是空格子;如果 \(S_i\) 的第 \(j\) 个字符是 #,则 \((i,j)\) 是炸弹格子。

对于一个空格子 \((i,j)\),如果第 \(i\) 行整行和第 \(j\) 列整列中都没有炸弹格子,则该格子被称为安全空格子

在一次移动中,你可以从当前格子移动到上、下、左、右四个方向中相邻的空格子(不能移动到炸弹格子)。请找出满足以下条件的空格子 \((i,j)\) 的数量:

  • \((i,j)\) 出发,最多经过 \(K\) 次移动,能够到达一个安全空格子

数据范围

  • \(1 \le H,W \le 5\times 10^5\)
  • \(H\times W \le 5\times 10^5\)
  • \(0 \le K \le H\times W-1\)
  • \(S_i\) 的长度为 \(W\),且仅由 .# 组成

思路

由于移动并不是单向的,因此问有多少个空格子能最多移动 \(K\) 步到达安全空格子,也就是问从某个安全空格子出发,最多移动 \(K\) 步能到达的空格子数量有多少个。

这明显是一个多源点且限制步数的BFS问题,只需要一次性将所有安全空格子作为搜索起点,然后宽搜求能到达的空格子数量即可。

而对于安全空格子的判断,只需对行和列分别开一个计数数组 cntX[i]cntY[j],用于记录对应行和对应列上的炸弹格子数量。对于单元格 \((i, j)\),如果 cntX[i] == 0 && cntY[j] == 0 则说明这是一个安全空格子

判断的时间复杂度 \(O(H \cdot W)\),BFS 过程每个格子只需看一遍,时间复杂度 \(O(H \cdot W)\)

总时间复杂度 \(O(H \cdot W)\)

代码

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

const int dx[4] = {-1, 1, 0, 0};
const int dy[4] = {0, 0, -1, 1};

int H, W, K;
vector<vector<char>> mp;
vector<int> cntX, cntY; // 统计每行每列的炸弹格子数量

struct point
{
    int x, y, step;
};
vector<vector<bool>> vis; // 标记每个点是否已搜过

int main()
{
    cin >> H >> W >> K;
    
    mp = vector<vector<char>>(H + 1, vector<char>(W + 1));
    vis = vector<vector<bool>>(H + 1, vector<bool>(W + 1, false));
    cntX = vector<int>(H + 1, 0);
    cntY = vector<int>(W + 1, 0);
    
    for(int i = 1; i <= H; i++)
        for(int j = 1; j <= W; j++)
        {
            cin >> mp[i][j];
            if(mp[i][j] == '#')
                cntX[i]++, cntY[j]++;
        }
    
    queue<point> q;
    for(int i = 1; i <= H; i++)
        for(int j = 1; j <= W; j++)
            if(cntX[i] == 0 && cntY[j] == 0) // (i, j) 为安全空格子
            {
                q.push({i, j, 0});
                vis[i][j] = true;
            }
    
    while(!q.empty())
    {
        point u = q.front();
        q.pop();
        for(int i = 0; i < 4; i++)
        {
            point v = u;
            v.x += dx[i];
            v.y += dy[i];
            v.step++;
            
            if(v.x < 1 || v.y < 1 || v.x > H || v.y > W) // 限制坐标
                continue;
            if(v.step > K) // 限制步数
                continue;
            if(mp[v.x][v.y] == '#') // 限制不走到炸弹上
                continue;
            if(vis[v.x][v.y]) // 限制不重复搜索
                continue;
            
            q.push(v);
            vis[v.x][v.y] = true;
        }
    }
    
    int ans = 0; // 求被搜过的点的总数
    for(int i = 1; i <= H; i++)
        for(int j = 1; j <= W; j++)
            if(vis[i][j])
                ans++;
    cout << ans;
    
    return 0;
}

E - Odd Cycle

  • 预估难度:普及+/提高-
  • 标签:图论、深度优先搜索

题意

给定一个包含 \(N\) 个顶点(编号为 \(1\)\(N\))和 \(M\) 条边的简单连通无向图。第 \(i\) 条边连接顶点 \(a_i\)\(b_i\)

请判断图中是否存在由奇数个顶点组成的环。如果存在,请找出其中一个这样的环。

形式化地说,请判断是否存在一个整数序列 \((v_1, v_2, \ldots, v_K)\) 满足以下所有条件。如果存在,请找出其中一个这样的序列:

  • \(K\) 是一个至少为 \(3\) 的奇数。
  • \(v_1, v_2, \ldots, v_K\) 互不相同。
  • 对于每一个满足 \(1 \leq i \leq K\) 的整数 \(i\),顶点 \(v_i\)\(v_{i+1}\) 之间存在一条边(其中规定 \(v_{K+1} = v_1\))。

本题包含 \(T\) 组测试用例,请依次求解。

数据范围

  • \(1 \le T \le 2\times10^5\)
  • \(1 \le N, M \le 2\times10^5\)
  • \(N\) 的总和与 \(M\) 的总和均不超过 \(2 \times 10^5\)
  • \(1 \le a_i,b_i \le N\)
  • \(a_i\ne b_i\)
  • 给定的图一定是一张简单连通无向图

思路

考虑构建无向图的深度优先搜索树,也就是按照深搜的方式,以某个点(点 \(1\))作为根结点开始搜索,将所有非父结点的邻接点均看作是子结点。

但由于图中存在环,因此可能会出现某个点 \(u\) 的子结点 \(v\) 反而是其祖先的情况,也就是遇到了返祖边

  • 这一步在代码实现过程中,如果搜索到的下一个点不是当前点的父结点,那么只要下一个点还没被搜过,就可以直接作为子结点继续搜索,否则便可以断定出现了返祖边。

但很明显,只要这样的返祖边 \(u \rightarrow v\) 出现,也就代表着从 \(v\)\(u\) 的树链在拼上这条返祖边后能够形成完整的环。

因此,为了在出现环时找出树链上的所有点,我们可以在深度优先搜索的同时,借助一个栈来维护根结点到当前搜索的这个点的路径列表,这只需要每次搜索一个点时让当前点进栈,搜索完后再让当前点正常出栈即可。如此,我们便可以在发现返祖边 \(u \rightarrow v\) 时,将所有在 \(v\) 之后搜到的点全部出栈,来当作环的组成部分。

但本题还要求我们找的环必须是奇环,因此只是找到返祖边还不够。我们还可以在搜索的同时再维护一个深度数组 dep[i] 用于表示每个点在搜索树上的深度。那么在发现返祖边 \(u \rightarrow v\) 时,由于 \(u, v\) 都应在环内,因此只需要保证 dep[u]dep[v] 的奇偶性相同(深度差值为偶数),即可断定环上总点数为奇数。此时再通过出栈操作找出环上的所有点作为答案即可。

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

代码

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

int N, M;
vector<int> G[200005];

stack<int> sk;
bool vis[200005];
int dep[200005]; // 假设以 1 为根,去除所有返祖边影响后,记录 i 的深度

vector<int> ans; // 找到环时存答案

void dfs(int u, int fa)
{
    vis[u] = true;
    dep[u] = dep[fa] + 1;
    sk.push(u);
    
    for(int v : G[u])
    {
        if(v == fa)
            continue;
        if(!ans.empty()) // 已经找到环,不用再继续搜索,写这里防止回溯时对 ans 再次修改
            return;
        
        if(!vis[v]) // v 点没搜过,当作 u 的子结点
            dfs(v, u);
        else // v 点已搜过,要么重复搜索,要么说明是 u 的祖先
        {
            // 若 u -> v 作为返祖边,则形成环
            // 由于 u v 均包含在环内,因此深度差为偶数时,才说明是奇环
            if(dep[v] < dep[u] && (dep[v] - dep[u]) % 2 == 0)
            {
                // 将栈内所有在 v 之后搜到的点全部弹出,表示环上的点
                while(!sk.empty() && sk.top() != v)
                {
                    ans.push_back(sk.top());
                    sk.pop();
                }
                ans.push_back(v);
                return;
            }
        }
    }
    
    if(ans.empty()) // 没找到答案时可正常出栈,已经找到答案时需注意可能已被出栈
        sk.pop();
}

void solve()
{
    cin >> N >> M;
    
    // 多组数据清空
    for(int i = 1; i <= N; i++)
    {
        G[i].clear();
        dep[i] = 0;
        vis[i] = false;
    }
    ans.clear();
    while(!sk.empty())
        sk.pop();
    
    for(int i = 1; i <= M; i++)
    {
        int a, b;
        cin >> a >> b;
        G[a].push_back(b);
        G[b].push_back(a);
    }
    
    dfs(1, 0);
    
    if(ans.empty())
        cout << "-1\n";
    else
    {
        cout << ans.size() << "\n";
        for(int i : ans)
            cout << i << " ";
        cout << "\n";
    }
}

int main()
{
    int T;
    cin >> T;
    while(T--)
        solve();
    
    return 0;
}
posted @ 2026-08-22 21:50  StelaYuri  阅读(66)  评论(0)    收藏  举报