AtCoder Beginner Contest 218【A - G】
比賽連結:https://atcoder.jp/contests/abc218/tasks
A - Weather Forecast
題意
判斷一個字串的第 \(n\) 個字元是否為 o
。
題解
模擬。
程式碼
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; string s; cin >> s; cout << (s[n - 1] == 'o' ? "Yes" : "No") << "\n"; return 0; }
B - qwerty
題意
依次輸出第 \(x_i\) 個小寫字母。
題解
模擬。
程式碼
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); for (int i = 0; i < 26; i++) { int x; cin >> x; cout << char('a' + x - 1); } return 0; }
C - Shapes
題意
給出兩個 \(n \times n\) 的圖形,判斷能否通過旋轉(每次九十度)、平移使得二者重合。
- \(1 \le n \le 200\)
題解
列舉旋轉的次數,若二者可以平移至重合則所有橫縱座標之差相等。
程式碼
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<string> MP1(n), MP2(n); for (int i = 0; i < n; i++) { cin >> MP1[i]; } for (int i = 0; i < n; i++) { cin >> MP2[i]; } auto rotate = [](int n, vector<string>& MP) { // n x n 90 degree clockwise auto t_MP(MP); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { t_MP[j][n - 1 - i] = MP[i][j]; } } MP = t_MP; }; bool ok = false; for (int t = 0; t < 4; t++) { rotate(n, MP1); vector<int> x1, y1, x2, y2; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (MP1[i][j] == '#') { x1.push_back(i), y1.push_back(j); } if (MP2[i][j] == '#') { x2.push_back(i), y2.push_back(j); } } } if (x1.size() != x2.size()) { continue; } bool trans = true; for (int i = 0; i < (int)x1.size(); i++) { if (x1[i] - x2[i] != x1[0] - x2[0] or y1[i] - y2[i] != y1[0] - y2[0]) { trans = false; } } if (trans) { ok = true; } } cout << (ok ? "Yes" : "No") << "\n"; return 0; }
D - Rectangles
題意
給出平面上 \(n\) 個不等的點,判斷可以形成多少與橫縱座標軸平行的矩形。
- \(4 \le n \le 2000\)
題解
列舉對角頂點即可,同一個矩形會被主副對角線列舉兩次,所以最終答案還要再除以二。
程式碼
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> x(n), y(n);
map<pair<int, int>, bool> mp;
for (int i = 0; i < n; i++) {
cin >> x[i] >> y[i];
mp[{x[i], y[i]}] = true;
}
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (x[i] == x[j] or y[i] == y[j]) {
continue;
}
if (mp[{x[i], y[j]}] and mp[{x[j], y[i]}]) {
++ans;
}
}
}
cout << ans / 2 << "\n";
return 0;
}
E - Destruction
題意
給出一個 \(n\) 個點 \(m\) 條邊的無向連通圖,每次可以移除一條邊,收益為該邊的權值 \(c_i\) 。
問在保證圖連通的情況下,最大收益為多少。
-
\(2 \le n \le 2 \times 10^5\)
-
\(n - 1 \le m \le 2 \times 10^5\)
-
\(-10^9 \le c_i \le 10^9\)
題解
類似最小生成樹的思想,只不過在計算收益時只考慮非負權邊。
程式碼
#include <bits/stdc++.h>
using namespace std;
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 main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<tuple<int, int, int>> edge(m);
long long ans = 0;
for (auto& [w, u, v] : edge) {
cin >> u >> v >> w;
--u, --v;
if (w >= 0) {
ans += w;
}
}
sort(edge.begin(), edge.end());
dsu dsu(n);
long long sub = 0;
for (auto [w, u, v] : edge) {
if (dsu.Diff(u, v)) {
dsu.Union(u, v);
if (w >= 0) {
sub += w;
}
}
}
cout << ans - sub << "\n";
return 0;
}
F - Blocked Roads
題意
給出一個 \(n\) 個點 \(m\) 條邊的有向圖,問如果移除第 \(i\) 條邊,結點 \(1\) 與結點 \(n\) 的最短距離為多少。
- \(2 \le n \le 400\)
- \(1 \le m \le n(n - 1)\)
題解
\(Dijkstra\) 有 \(O_{(n^2)}\) 和 \(O_{((n + m)log_n)}\) 兩種實現方法,本題中結點數 \(n\) 較少,所以可以先確定一條從 \(1\) 到 \(n\) 的最短路徑,之後若第 \(i\) 條邊不在此路徑中,直接輸出 \(dis_{(1, n)}\) 即可,否則移除該邊後重跑一遍 \(O_{(n^2)}\) 的 \(Dijkstra\) ,因為路徑中最多含有 \(n - 1\) 條邊,所以最壞時間複雜度為 \(O_{(n^3)}\) 。
Tips
移除邊後記得回溯。
程式碼
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<vector<int>> edge(n, vector<int> (n, 1e9));
vector<int> u(m), v(m);
for (int i = 0; i < m; i++) {
cin >> u[i] >> v[i];
--u[i], --v[i];
edge[u[i]][v[i]] = 1;
}
vector<int> dis(n), pre(n, -1);
auto Dijkstra = [&]() {
fill(dis.begin(), dis.end(), 1e9);
dis[0] = 0;
queue<int> que;
que.push(0);
vector<bool> vis(n);
vis[0] = true;
while (not que.empty()) {
int u = que.front();
que.pop();
for (int v = 0; v < n; v++) {
if (not vis[v] and dis[u] + edge[u][v] < dis[v]) {
dis[v] = dis[u] + edge[u][v];
vis[v] = true;
pre[v] = u;
que.push(v);
}
}
}
};
Dijkstra();
vector<vector<bool>> used(n, vector<bool> (n));
for (int u = pre[n - 1], v = n - 1; u != -1; v = u, u = pre[u]) {
used[u][v] = true;
}
for (int i = 0; i < m; i++) {
if (used[u[i]][v[i]]) {
edge[u[i]][v[i]] = 1e9;
Dijkstra();
cout << (dis[n - 1] == 1e9 ? -1 : dis[n - 1]) << "\n";
edge[u[i]][v[i]] = 1;
Dijkstra();
} else {
cout << (dis[n - 1] == 1e9 ? -1 : dis[n - 1]) << "\n";
}
}
return 0;
}
G - Game on Tree 2
題意
給出一棵有 \(n\) 個結點的樹,每個結點的權值為 \(a_i\) 。
初始時結點 \(1\) 處有一枚棋子,Alice 先手,Bob 後手,每次可以將棋子從當前結點移至任一未訪問過的子結點。
最終收益為所經結點權值 multiset
的中位數,Alice 想要將其最大化,Bob 想要將其最小化,雙方均採取最優策略,問最終集合的中位數會是多少。
中位數的定義
若集合大小為奇數,則為中間的數,否則為中間兩個數的平均值。- \(2 \le n \le 10^5\)
- \(2 \le a_i \le 10^9\) , \(a_i\) 均為偶數
題解
假設結點 \(1\) 為根節點,那麼每個葉結點的中位數都是唯一確定的,同時可以由深度確定本輪操作者,利用 \(dfs\) + 回溯 進行樹上 \(dp\) 即可。
快速確定集合的中位數可以用 座標壓縮 + 權值樹狀陣列,也可以用兩個 multiset
分別儲存前後一半的值。
程式碼
#include <bits/stdc++.h>
using namespace std;
struct Kth_multiset {
multiset<int> mst1, mst2; // always mst2.size() == mst1.size or mst2.size() == mst1.size + 1 for example: mst1:{2, 4} mst2:{4, 6, 8}
void balance() {
if (mst2.size()) {
mst1.insert(*mst2.begin());
mst2.erase(mst2.begin());
}
while (mst1.size() > mst2.size()) {
mst2.insert(*mst1.rbegin());
mst1.erase(prev(mst1.end()));
}
}
void insert(int val) { // all vals should be even
mst2.insert(val);
balance();
}
void erase(int val) {
if (mst2.find(val) != mst2.end()) {
mst2.erase(mst2.find(val));
} else {
mst1.erase(mst1.find(val));
}
balance();
}
int find_kth() {
if (mst2.size() == mst1.size() + 1) {
return *mst2.begin();
} else {
return (*mst1.rbegin() + *mst2.begin()) / 2; // because of here
}
}
} K;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
vector<vector<int>> G(n);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
--u, --v;
G[u].push_back(v);
G[v].push_back(u);
}
vector<int> dp(n);
function<void(int, int, int)> dfs = [&](int u, int p, int d) {
K.insert(a[u]);
int mi = 2e9, mx = 0;
for (auto v : G[u]) {
if (v != p) {
dfs(v, u, d + 1);
mi = min(mi, dp[v]), mx = max(mx, dp[v]);
}
}
if (mx == 0) { // is leaf
dp[u] = K.find_kth();
} else {
dp[u] = d & 1 ? mi : mx;
}
K.erase(a[u]);
};
dfs(0, -1, 0);
cout << dp[0] << "\n";
return 0;
}