AtCoder Beginner Contest 464 ABCDE

A - Decisive Battle

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

题意

给定一个长度为奇数的字符串 \(S\),字符串仅由 EW 组成。

如果给定字符串中 E 的数量比 W 多,则输出 East,否则输出 West

代码

void solve()
{
    string s;
    cin >> s;
    int w = count(s.begin(), s.end(), 'W');
    int e = s.size() - w;
    if(w > e)
        cout << "West";
    else
        cout << "East";
}

B - Crop

  • 预估难度:普及-
  • 标签:二维数组

题意

现有一张高度为 \(H\) 像素、宽度为 \(W\) 像素的黑白图像。

图像中第 \(i\) 行(从上往下数)、第 \(j\) 列(从左往右数)的像素颜色由字符 \(C_{i,j}\) 给出,其中 . 表示白色,# 表示黑色。

请从该图像的顶部、底部、左侧和右侧边缘,删除所有像素均为白色的行和列。具体需按以下顺序执行操作:

  1. 只要图像的最顶行全部为白色,就不断删除最顶行。
  2. 只要图像的最底行全部为白色,就不断删除最底行。
  3. 只要图像的最左列全部为白色,就不断删除最左列。
  4. 只要图像的最右列全部为白色,就不断删除最右列。

请输出处理完成后的图像。

题目保证给定的图像中至少包含一个黑色像素。

  • \(1 \le H, W \le 50\)

思路

根据题意,只有当边界上全为白色格子时,才会向内部继续缩小边界。

换句话说,只要某个边界上出现了至少一个黑色格子,那么这个边界就不会继续向内缩小了。

因此我们只需要找出原图像中所有黑色格子出现的最上、最下、最左、最右这四个位置,分别记作 \(t\) 行、\(b\) 行、\(l\) 列、\(r\) 列,便可以发现这四个边界对应的子矩形就是我们要找的答案,输出即可。

时间复杂度 \(O(H \times W)\)

代码

int n, m;
char mp[55][55];

void solve()
{
    cin >> n >> m;
    
    // 找黑色点在地图中的上、下、左、右边界
    int t = 1e5, b = 0;
    int l = 1e5, r = 0;
    
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
        {
            cin >> mp[i][j];
            if(mp[i][j] == '#') // 第 i 行第 j 列存在黑色点
            {
                t = min(t, i); // 上边界
                b = max(b, i); // 下边界
                l = min(l, j); // 左边界
                r = max(r, j); // 右边界
            }
        }
    
    for(int i = t; i <= b; i++)
    {
        for(int j = l; j <= r; j++)
            cout << mp[i][j];
        cout << "\n";
    }
}

C - Plumage Palette

  • 预估难度:普及-
  • 标签:计数数组

题意

高桥在接下来的 \(M\) 天里观察了 \(N\) 只鸟,它们的编号为 \(1, 2, \dots, N\)

高桥观察到的每只鸟一开始都拥有一种颜色,编号为 \(1, 2, \dots, N\) 之一。并且这些鸟有一个有趣的特征:它们的颜色可能会在观察期间发生变化。

对于第 \(i\) 只鸟,在第 \(D_i - 1\) 天及之前的观察中,它的颜色为 \(A_i\);在第 \(D_i\) 天及之后的观察中,它的颜色变为 \(B_i\)。特殊的:

  • 如果 \(D_i = 1\),则该鸟从第 \(1\) 天的观察起颜色即为 \(B_i\)
  • 如果 \(A_i = B_i\),则该鸟在整个观察期间颜色没有发生变化。

对于每个 \(j = 1, 2, \dots, M\),请分别求出在第 \(j\) 天时,共有多少种不同的鸟的颜色。

  • \(1 \le N \le 3 \times 10^5\)
  • \(1 \le M \le 3 \times 10^5\)
  • \(1 \le A_i, B_i \le N\)
  • \(1 \le D_i \le M\)

思路

首先可以借助计数数组 \(\text{cnt}[i]\) 来统计当前颜色 \(i\) 对应的鸟的数量

如果有一只鸟从颜色 \(A_i\) 变为 \(B_i\),我们可以看作是少了一只颜色为 \(A_i\) 的鸟,并多了一只颜色为 \(B_i\) 的鸟。

先考虑多一只颜色为 \(x\) 的鸟。此时对于计数数组而言,只需要 \(\text{cnt}[x]:=\text{cnt}[x]+1\) 即可。但在执行之前如果一只颜色为 \(x\) 的鸟都不存在(即 \(\text{cnt}[x] = 0\)),那就说明这只鸟的出现导致颜色的种类数 \(+1\) 了。

再考虑少一只颜色为 \(x\) 的鸟。此时对于计数数组而言,只需要 \(\text{cnt}[x]:=\text{cnt}[x]-1\) 即可。但在执行之后如果一只颜色为 \(x\) 的鸟都不存在了,那就说明这只鸟的消失导致颜色的种类数 \(-1\) 了。

因此我们只需要再借助一个变量 \(\text{tot}\) 用来统计当前颜色种类数,在鸟的颜色改变的过程中同时维护这个变量即可。

至于在第 \(j = 1, 2, \ldots, M\) 的每一天分别有哪些鸟的颜色发生改变了,可以借助 vector 容器结合结构体来统计每一天的所有变化情况,或是借助结构体排序+双指针来处理均可。

以下代码采用了 vector 存储的方式,时间复杂度 \(O(N + M)\)

代码

struct bird
{
    int a, b; // 从 a 变化到 b
};

vector<bird> G[300005]; // G[i] 存第 i 天中所有要变化颜色的鸟的变化情况

int cnt[300005]; // cnt[i] 表示颜色为 i 的鸟的数量
int tot = 0; // tot 表示目前总共有多少种颜色出现

void add(int x) // 多了一只颜色为 x 的鸟
{
    if(cnt[x] == 0) // 说明这只鸟是这种颜色出现的第一只鸟,颜色总数 +1
        tot++;
    cnt[x]++;
}

void del(int x) // 少了一只颜色为 x 的鸟
{
    cnt[x]--;
    if(cnt[x] == 0) // 说明这只鸟是这种颜色出现的最后一只鸟,颜色总数 -1
        tot--;
}

void solve()
{
    int n, m;
    cin >> n >> m;
    
    for(int i = 1; i <= n; i++)
    {
        int a, d, b;
        cin >> a >> d >> b;
        add(a); // 假设所有鸟都还没变化,颜色 a 的鸟 +1只
        G[d].push_back({a, b}); // 这只鸟在第 d 天才会变化
    }
    
    for(int i = 1; i <= m; i++)
    {
        for(bird x : G[i]) // 将所有第 i 天会变化的情况处理一遍
        {
            // 有一只鸟从颜色 x.a 变为 x.b
            del(x.a);
            add(x.b);
        }
        cout << tot << "\n";
    }
}

D - Celester

  • 预估难度:普及/提高-
  • 标签:动态规划

题意

接下来 \(N\) 天的天气情况由一个字符串 \(S\) 给出。
\(i\) 个字符描述第 \(i\) 天的天气,其中 S 表示晴天,R 表示雨天。

一开始,你的幸福度为 \(0\)

你可以执行以下操作任意次(包括 \(0\) 次):

  • 选择某一天,然后将这一天的天气反转(晴天变雨天,雨天变晴天)。
  • 假设你选择的是第 \(i\) 天(\(1 \le i \le N\)),那么你的幸福度将减少 \(X_i\)

在执行完所有操作后,你的幸福度会根据最终的天气情况按以下规则增加:

  • 对于每个整数 \(i\)\(1 \le i \le N-1\)),如果修改后第 \(i\) 天的天气为雨天,且第 \(i+1\) 天的天气为晴天,则你的幸福度增加 \(Y_i\)

请计算你所能达到的幸福度的最大值。

\(T\) 组数据。

  • \(1 \le T \le 10^4\)
  • \(2 \le N \le 2 \times 10^5\)
  • \(1 \le X_i, Y_i \le 10^9\)
  • \(N\) 的总和不超过 \(2 \times 10^5\)

思路

对每一天的天气,我们可以决策是否要进行修改。因此可以考虑线性动态规划,记 \(\text{dp}[i]\) 表示如果只考虑到第 \(i\) 天过,能获得的最大幸福度是多少。

边界初始状态 \(\text{dp}[0] = 0\)。如果只有一天明显不更改最优,因此 \(\text{dp}[1] = 0\)

对于 \(i \ge 2\)\(\text{dp}[i]\),对第 \(i\) 天的天气是否需要修改进行决策:

  • 如果不修改第 \(i\) 天的天气,明显此时 \(\text{dp}[i] = \text{dp}[i-1]\)。但如果原本最后这两天的天气就是先雨后晴(\(\text{S}[i-1]=\) S\(\text{S}[i]=\) R),那么我们需要在保证前一天也没有被修改的前提下,把这两天的幸福度贡献 \(Y_{i-1}\) 加进来。

\[\text{dp}[i] = \max \left \{ \begin{aligned} &\text{dp}[i-1] \\ &\text{dp}[i-2] + Y_i \quad \text{当最后两天原本就是先雨后晴} \end{aligned} \right . \]

  • 如果要修改第 \(i\) 天的天气,由于 \(\text{dp}[i]\) 在定义中表示只考虑到第 \(i\) 天过,所以第 \(i\) 天肯定只能改成晴天,顺便把前一天改为雨天,不然相当于只是单方面减少幸福度,而无法获得任何贡献。

\[\text{dp}[i] = \text{dp}[i-2] - (\text{第i天改为晴天的代价}) - (\text{第i-1天改为雨天的代价}) + Y_{i-1} \]

以上两种决策情况取最大值作为答案即可。

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

代码(单组数据)

typedef long long ll;

int n, x[200005], y[200005];
string s;

ll dp[200005];

void solve()
{
    cin >> n >> s;
    s = " " + s;
    for(int i = 1; i <= n; i++)
        cin >> x[i];
    for(int i = 1; i < n; i++)
        cin >> y[i];
    
    dp[0] = dp[1] = 0;
    for(int i = 2; i <= n; i++)
    {
        // 如果不修改第 i 天的天气
        ll p = dp[i - 1];
        // 如果原本就是先雨天后晴天,那么在保证前一天也不改的基础上,可以把 y[i-1] 加入答案
        if(s[i - 1] == 'R' && s[i] == 'S')
            p = max(p, dp[i - 2] + y[i - 1]);
        
        // 如果要修改第 i 天的天气
        // 为了让前 i 天答案最大,明显 i-1 和 i 这两天应该被修改为 先雨天后晴天
        ll q = dp[i - 2] + y[i - 1];
        if(s[i - 1] != 'R') // 改第 i-1 天
            q -= x[i - 1];
        if(s[i] != 'S')     // 改第 i 天
            q -= x[i];
        
        dp[i] = max(p, q);
    }
    
    cout << dp[n] << "\n";
}

E - Fill-Rect Query

  • 预估难度:普及/提高-
  • 标签:动态规划

题意

现有一个 \(H \times W\) 的网格。
初始时,网格中的每个单元格上都写着字符 A

下面将第 \(i\) 行第 \(j\) 列的单元格记为 \((i, j)\)

接下来将依次执行 \(Q\) 次操作。对于第 \(i\) 次操作:

  • 给定 \(R_i, C_i, X_i\),然后将左上角为 \((1, 1)\)、右下角为 \((R_i, C_i)\) 的矩形区域内的所有单元格,全部改为大写英文字母 \(X_i\)

请输出执行完所有操作后的网格。

  • \(1 \le H, W\)
  • \(H \times W \le 10^6\)
  • \(1 \le Q \le 2 \times 10^5\)
  • \(1 \le R_i \le H\)
  • \(1 \le C_i \le W\)

思路

考虑最终每个单元格内的颜色与每次操作之间的关系。

可以发现,每次操作会改变的单元格一定是在 \((R_i, C_i)\) 左上角的整个矩形区域内的单元格。

也就是说,只要网格中的某个单元格的行 \(\gt R_i\),或者列 \(\gt C_i\),那么这个单元格就不会受到这次操作的影响。

而从单元格的角度出发,对于单元格 \((x, y)\),只有当 \((R_i, C_i)\) 在我的右下角时,我才会受到这次操作的影响。


下文将第 \(i\) 次操作描述为“发生在位置 \((R_i, C_i)\) 的操作”。

因为后面执行的操作会把前面的操作覆盖掉,因此对于每个单元格 \((x, y)\),我们只需要知道所有 \((R_i, C_i)\)\((x, y)\) 的右下角的操作中最晚执行的那一次操作对应的字符,那么这个字符就是答案。

先对于每个位置 \((i, j)\),记录 \(\text{last}[i][j]\) 表示在当前位置发生的所有操作的最晚时间(最大下标)。

考虑动态规划,记 \(\text{dp}[i][j]\) 表示在 \((i, j)\) 右下角出现的所有操作的最晚时间。

我们可以把 \((i, j)\) 的右下角子矩形拆为三部分:

  • \((i, j)\) 单点
  • \((i+1, j)\) 的右下角
  • \((i, j+1)\) 的右下角

明显后两部分是存在重叠部分的(即 \((i+1, j+1)\) 的右下角)。但因为我们只需要知道“最晚时间”,时间取最大值即可,因此重叠部分不影响答案。

三者对应的答案分别为:

  • \(\text{last}[i][j]\)
  • \(\text{dp}[i+1][j]\)
  • \(\text{dp}[i][j+1]\)

三者 \(\max\) 即可获得 \(\text{dp}[i][j]\) 的答案,且明显动态规划的顺序应当按二维倒序顺序进行。

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

代码

int n, m, q, r[200005], c[200005];
char x[200005];

vector<vector<int>> last;
// last[i][j] 记录在该位置发生的所有操作的最大下标(最后一次操作的时间)
// 如果没有则为 0
vector<vector<int>> dp;
// dp[i][j] 表示在 (i, j) 的右下角执行的所有操作的最大下标
vector<vector<char>> mp;
// 答案

void solve()
{
    cin >> n >> m >> q;
    
    last = vector<vector<int>>(n + 2, vector<int>(m + 2, 0));
    dp = vector<vector<int>>(n + 2, vector<int>(m + 2, 0));
    mp = vector<vector<char>>(n + 2, vector<char>(m + 2, 'A')); // 初始化为 A
    
    for(int i = 1; i <= q; i++)
    {
        cin >> r[i] >> c[i] >> x[i];
        last[r[i]][c[i]] = i; // 记录在该位置发生的操作的最大下标
    }
    
    for(int i = n; i >= 1; i--)
        for(int j = m; j >= 1; j--)
        {
            dp[i][j] = last[i][j]; // 先取当前位置发生的操作中的最大时间
            if(i + 1 <= n)
                dp[i][j] = max(dp[i][j], dp[i + 1][j]);
            if(j + 1 <= m)
                dp[i][j] = max(dp[i][j], dp[i][j + 1]);
            
            if(dp[i][j] != 0) // 说明该位置至少被一次操作影响到
                mp[i][j] = x[dp[i][j]];
        }
    
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
            cout << mp[i][j];
        cout << "\n";
    }
}
posted @ 2026-06-27 21:42  StelaYuri  阅读(23)  评论(0)    收藏  举报