比赛链接: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") << "
";
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 imes 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") << "
";
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 << "
";
return 0;
}
E - Destruction
题意
给出一个 (n) 个点 (m) 条边的无向连通图,每次可以移除一条边,收益为该边的权值 (c_i) 。
问在保证图连通的情况下,最大收益为多少。
-
(2 le n le 2 imes 10^5)
-
(n - 1 le m le 2 imes 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);
for (auto [w, u, v] : edge) {
if (dsu.Diff(u, v)) {
dsu.Union(u, v);
if (w >= 0) {
ans -= w;
}
}
}
cout << ans << "
";
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]) << "
";
edge[u[i]][v[i]] = 1;
Dijkstra();
} else {
cout << (dis[n - 1] == 1e9 ? -1 : dis[n - 1]) << "
";
}
}
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] << "
";
return 0;
}