AtCoder Beginner Contest 464 ABCDE
A - Decisive Battle
- 预估难度:
入门 - 标签:字符串
题意
给定一个长度为奇数的字符串 \(S\),字符串仅由 E 和 W 组成。
如果给定字符串中 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 \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}\) 加进来。
- 如果要修改第 \(i\) 天的天气,由于 \(\text{dp}[i]\) 在定义中表示只考虑到第 \(i\) 天过,所以第 \(i\) 天肯定只能改成晴天,顺便把前一天改为雨天,不然相当于只是单方面减少幸福度,而无法获得任何贡献。
以上两种决策情况取最大值作为答案即可。
单组数据时间复杂度 \(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";
}
}

浙公网安备 33010602011771号