比賽鏈接: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\) 打上標記 \((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