比赛链接:https://atcoder.jp/contests/abc219/tasks
A - AtCoder Quiz 2
题意
给出一个分数 \(x\) ,共分为四级:
- \(0 \le x \lt 40\)
- \(40 \le x \lt 70\)
- \(70 \le x \lt 90\)
- \(90 \le x\)
输出 \(x\) 到下一级所需的分数,如已是*输出 expert
。
题解
模拟。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int x;
cin >> x;
if (x >= 90) {
cout << "expert" << "\n";
} else {
cout << (x < 40 ? 40 - x : (x < 70 ? 70 - x : 90 - x)) << "\n";
}
return 0;
}
B - Maritozzo
题意
给出 \(3\) 个字符串 \(s_i\) ,按字符串 \(t\) 中的顺序依次输出。
题解
模拟。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
vector<string> s(3);
for (int i = 0; i < 3; i++) {
cin >> s[i];
}
string t;
cin >> t;
for (auto ch : t) {
cout << s[ch - ‘1‘];
}
return 0;
}
C - Neo-lexicographic Ordering
题意
将 \(n\) 个字符串按照给定的某种字典序排序。
题解
模拟。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string mp;
cin >> mp;
int n;
cin >> n;
vector<pair<string, string>> s(n);
for (auto& [x, y] : s) {
cin >> y;
x = y;
for (auto& ch : x) {
ch = mp.find(ch) + ‘a‘;
}
}
sort(s.begin(), s.end());
for (auto [x, y] : s) {
cout << y << "\n";
}
return 0;
}
D - Strange Lunchbox
题意
有 \(n\) 个物品,每个物品的价值为 \((a_i, b_i)\) ,计算最少要选取多少个物品使得 \(\sum a \ge x\) \(\sum b \ge y\) 。
- \(1 \le n \le 300\)
- \(1 \le a_i, b_i \le 300\)
- \(1 \le x, y \le 300\)
题解
\(01dp\) ,设 \(dp[i][j]\) 为 \(\sum a = i\) \(\sum b = j\) 的最小花费。
注意对于 \(\sum a \ge x\) \(\sum b \ge y\) 的状态的压缩。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
int x, y;
cin >> x >> y;
vector<int> a(n), b(n);
for (int i = 0; i < n; i++) {
cin >> a[i] >> b[i];
}
constexpr int N = 305;
vector<vector<int>> dp(N, vector<int> (N, 1e9));
dp[0][0] = 0;
for (int i = 0; i < n; i++) {
auto next_dp(dp);
for (int j = 0; j < N; j++) {
for (int k = 0; k < N; k++) {
next_dp[min(N - 1, j + a[i])][min(N - 1, k + b[i])] = min(next_dp[min(N - 1, j + a[i])][min(N - 1, k + b[i])], dp[j][k] + 1);
}
}
dp = next_dp;
}
int ans = 1e9;
for (int i = x; i < N; i++) {
for (int j = y; j < N; j++) {
ans = min(ans, dp[i][j]);
}
}
cout << (ans == 1e9 ? -1 : ans) << "\n";
return 0;
}
E - Moat
题意
给出一个 \(4 \times 4\) 的 \(01\) 矩阵,计算有多少矩阵多边形可以包含所有 \(1\) 。
题解
矩阵多边形可以视为无内环的连通块,枚举每个块的选取状态即可,共 \(2^{16}\) 种情况。
关于连通块合法性的判断:
- 初始时有 \(16\) 个连通块,之后根据选取状态合并为全 \(0/1\) 连通块,为了判断是否有内环,可以将全 \(0\) 连通块合并到第 \(17\) 个外围连通块中,这样如果最后只剩两个连通块即说明当前选取状态合法。
代码
#include <bits/stdc++.h>
using namespace std;
const int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
struct dsu {
vector<int> fa, sz;
dsu(int n) : fa(n), sz(n) {
iota(fa.begin(), fa.end(), 0);
fill(sz.begin(), sz.end(), 1);
}
int Find(int x) {
return fa[x] == x ? x : fa[x] = Find(fa[x]);
}
void Union(int x, int y) {
x = Find(x), y = Find(y);
if (x == y) {
return;
}
if (sz[x] < sz[y]) {
swap(x, y);
}
sz[x] += sz[y];
fa[y] = x;
}
int Size(int x) {
return fa[x] == x ? sz[x] : sz[x] = sz[Find(x)];
}
bool diff(int x, int y) {
return Find(x) != Find(y);
}
int Groups() {
int res = 0;
for (int i = 0; i < (int)fa.size(); i++) {
if (fa[i] == i) {
++res;
}
}
return res;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
vector<vector<int>> a(4, vector<int> (4));
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
cin >> a[i][j];
}
}
int ans = 0;
for (int mask = 0; mask < (1 << 16); mask++) {
vector<vector<int>> b(4, vector<int> (4));
for (int i = 0; i < 16; i++) {
if (mask & (1 << i)) {
b[i / 4][i % 4] = 1;
}
}
bool ok = true;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (a[i][j] == 1 and b[i][j] == 0) {
ok = false;
}
}
}
if (not ok) {
continue;
}
dsu dsu(17);
for (int x = 0; x < 4; x++) {
for (int y = 0; y < 4; y++) {
for (int i = 0; i < 4; i++) {
int nx = x + dir[i][0], ny = y + dir[i][1];
if (not (0 <= nx and nx < 4 and 0 <= ny and ny < 4)) {
if (b[x][y] == 0) {
dsu.Union(x * 4 + y, 16);
}
} else {
if (b[nx][ny] == b[x][y]) {
dsu.Union(x * 4 + y, nx * 4 + ny);
}
}
}
}
}
if (dsu.Groups() == 2) {
++ans;
}
}
cout << ans << "\n";
return 0;
}
F - Cleaning Robot
题意
给出一个点在二维平面的运动轨迹 \(s\) ,初始时点在 \((0, 0)\) 处,问将 \(s\) 连续执行 \(k\) 次后共经过了多少不重复的点。
-
\(1 \le |s| \le 2 \times 10^5\) ,由
L
,R
,U
,D
组成 - \(1 \le k \le 10^{12}\)
题解
注意到后一次的运动轨迹是前一次的平移,设第一轮执行完 \(s\) 后途径的点的集合为 \((x_i, y_i)\) ,终点为 \((dx, dy)\) ,那么第 \(n\) 轮执行过程中所经的点即 \((x_i + (n - 1) \times dx, y_i + (n - 1) \times dy)\) 。
找出第一轮执行所经点的集合中可以通过加减 \((dx, dy)\) 重合的点,那么 \(k\) 次执行过程中这些点即可视为在同一条直线上平移,假设 \(dx, dy > 0\) ,这些点所经的不同点即右上角的点向右上方平移 \(k\) 次和它左下方的点两两间的平移。
对于同一直线上的点,可以根据加减 \((dx, dy)\) 得到 \(x > 0\) 且 \(x\) 最小的点为参照点分类。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s;
cin >> s;
long long k;
cin >> k;
vector<pair<int, int>> point;
int dx = 0, dy = 0;
point.emplace_back(dx, dy);
for (auto ch : s) {
if (ch == ‘L‘) {
--dx;
} else if (ch == ‘R‘) {
++dx;
} else if (ch == ‘U‘) {
--dy;
} else {
++dy;
}
point.emplace_back(dx, dy);
}
sort(point.begin(), point.end());
point.resize(unique(point.begin(), point.end()) - point.begin());
if (dx == 0 and dy == 0) {
cout << point.size() << "\n";
return 0;
}
if (dx == 0) {
swap(dx, dy);
for (auto& [x, y]: point) {
swap(x, y);
}
}
if (dx < 0) {
dx *= -1;
for (auto& [x, y]: point) {
x *= -1;
}
}
map<pair<int, int>, vector<long long>> mp;
auto normalize = [&](int x, int y) {
int d = 0;
if (x >= 0) {
d = x / dx;
} else {
d = -((-x + dx - 1) / dx);
}
x -= d * dx;
y -= d * dy;
mp[{x, y}].push_back(d);
};
for (auto [x, y] : point) {
normalize(x, y);
}
long long ans = 0;
for (auto [pr, vec] : mp) {
sort(vec.begin(), vec.end());
for (int i = 1; i < (int)vec.size(); i++) {
ans += min(k, vec[i] - vec[i - 1]);
}
ans += k;
}
cout << ans << "\n";
return 0;
}
G - Propagation
题意
给出一个有 \(n\) 个结点 \(m\) 条边的无向简单图,第 \(i\) 个结点初始时值为 \(i\) ,给出 \(q\) 次询问:
-
x
:将与结点 \(x\) 相邻的点赋为 \(x\) 现在的值
输出最后每个结点的值。
- \(1 \le n \le 2 \times 10^5\)
- \(0 \le m \le min(2 \times 10^5, \frac{n(n - 1)}{2})\)
- \(1 \le q \le 2 \times 10^5\)
题解
直接模拟的复杂度为 \(O_{(qm)}\) ,显然是不可接受的,考虑如何优化。
根据结点的度数分块:
- 若结点 \(x\) 的度数 \(\lt B\) ,即最多有 \(B\) 个结点与它相邻,那么更新 \(x\) 和相邻结点,时间复杂度为 \(O_{(B)}\)
- 若结点 \(x\) 的度数 \(\ge B\) ,只更新 \(x\) ,同时为 \(x\) 打上标记 \((val, i)\) ,表示在第 \(i\) 次询问中与 \(x\) 相邻的点需要赋为 \(val\) ,时间复杂度为 \(O_{(1)}\)
对于当前结点的更新,显然当前结点只能被相邻结点更新:
-
对于度数 \(\lt B\) 的相邻结点,已被更新无需遍历,时间复杂度为 \(O_{(1)}\)
-
对于度数 \(\ge B\) 的相邻结点,遍历寻找最近一次更新,因为这样的结点最多有 \(\frac{2m}{B}\) 个,所以时间复杂度为 \(O_{(\frac{2m}{B})}\)
此时时间复杂度为 \(O_{(q(B + \frac{2m}{B}))}\) ,易知当 \(B = \sqrt{2m}\) 时取得最小值 \(O_{(q\sqrt{m})}\) 。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, q;
cin >> n >> m >> q;
vector<vector<int>> G1(n);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
--u, --v;
G1[u].push_back(v);
G1[v].push_back(u);
}
const int B = sqrt(2 * m);
vector<vector<int>> G2(n);
for (int u = 0; u < n; u++) {
for (auto v : G1[u]) {
if ((int)G1[v].size() >= B) {
G2[u].push_back(v);
}
}
}
vector<pair<int, int>> ans(n), upd(n);
for (int i = 0; i < n; i++) {
ans[i] = upd[i] = {i, -1};
}
for (int i = 0; i < q; i++) {
int x;
cin >> x;
--x;
for (auto v : G2[x]) {
if (upd[v].second > ans[x].second) {
ans[x] = upd[v];
}
}
if ((int)G1[x].size() < B) {
for (auto v : G1[x]) {
ans[v] = {ans[x].first, i};
}
} else {
upd[x] = {ans[x].first, i};
}
}
for (int i = 0; i < n; i++) {
for (auto v : G2[i]) {
if (upd[v].second > ans[i].second) {
ans[i] = upd[v];
}
}
cout << ans[i].first + 1 << " \n"[i == n - 1];
}
return 0;
}
参考
https://atcoder.jp/contests/abc219/editorial/2667
https://atcoder.jp/contests/abc219/editorial/2665
https://atcoder.jp/contests/abc219/editorial/2664
https://atcoder.jp/contests/abc219/submissions/25932859
https://atcoder.jp/contests/abc219/submissions/25937953
https://atcoder.jp/contests/abc219/submissions/25942540
Sciseed Programming Contest 2021(AtCoder Beginner Contest 219)【A - G】