Educational Codeforces Round 109 (Rated for Div. 2)【ABCD】
比赛链接:https://codeforces.com/contest/1525
A. Potion-making
题意
开始时有一口空的大锅,每次操作可以向锅中加入 \(1\) 升魔法精华或 \(1\) 升水,问使魔法精华的比例为 \(k\ \%\) 最少需要操作多少次。
题解
\(k\ \% = \frac{k}{100}\) ,分子分母同除以公因子后比例不变,操作次数减少,最少操作次数即除以最大公因子,即 \(gcd(k, 100)\) 。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int k;
cin >> k;
cout << 100 / gcd(k, 100) << "\n";
}
return 0;
}
B. Permutation Sort
题意
给出一个大小为 \(n\) 的排列,每次操作可以将一个大小小于 \(n\) 的连续子数组重新排列,问将原排列变为升序至少需要操作多少次。
题解
- \(0\) 次:初始时为升序
- \(1\) 次:存在一个区间排序后整个数组为升序
- \(2\) 次: \(1\) 和 \(n\) 不同时位于尾部和首部
- \(3\) 次: \(1\) 和 \(n\) 同时位于尾部和首部
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
if (is_sorted(a.begin(), a.end())) {
cout << 0 << "\n";
} else {
bool once = false;
for (int l = 0; l < n; l++) {
for (int r = l + 1; r <= n; r++) {
if (l == 0 and r == n) {
continue;
}
vector<int> b(a);
sort(b.begin() + l, b.begin() + r);
if (is_sorted(b.begin(), b.end())) {
once = true;
}
}
}
cout << (once ? 1 : (a[0] == n and a[n - 1] == 1 ? 3 : 2)) << "\n";
}
}
return 0;
}
C. Robot Collisions
题意
有一数轴 \(x\) ,在 \(0\) 和 \(m\) 处有两面墙,两面墙内有 \(n\) 个机器人,坐标均为整数且两两不同。
机器人速度恒为 \(1\) 单位长度/秒,碰到墙后会向相反方向移动,若两个机器人同时到达某一整数坐标,则二者相撞并爆炸。
给出 \(n\) 个机器人的初始坐标和运动方向,计算每个机器人的相撞时间。
题解
首先只有初始时坐标奇偶性相同的机器人才有可能相撞。
其次最先相撞的是相邻且相向运动的两个机器人,即较小坐标向右,较大坐标向左的情况。
然后剩下的机器人运动状况为同时向左或同时向右且无交叉,以一前一后同时向左的情况为例:二者的相对距离在前者到达点 \(0\) 处之前是不变的,在前者到达点 \(0\) 处后二者开始相向运动。向右的情况同理。
最后一种可能相撞的情况是向左和向右运动的都还剩一个,这种情况可以看作二者从左右墙外对称点相向移动。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
vector<pair<int, char>> v(n);
for (int i = 0; i < n; i++) {
cin >> v[i].first;
}
for (int i = 0; i < n; i++) {
cin >> v[i].second;
}
map<int, int> ans;
auto solve = [&](const auto& v, int filter) {
vector<pair<int, char>> vec;
for (auto [x, dir] : v) {
if (x % 2 == filter) {
vec.emplace_back(x, dir);
}
}
sort(vec.begin(), vec.end());
vector<int> L, R;
for (auto [x, dir] : vec) {
if (dir == 'L') {
if (R.size()) {
int y = R.back(); R.pop_back();
ans[x] = ans[y] = (x - y) / 2;
} else {
L.push_back(x);
}
} else {
R.push_back(x);
}
}
reverse(L.begin(), L.end());
while (L.size() > 1) {
int x = L.back(); L.pop_back();
int y = L.back(); L.pop_back();
ans[x] = ans[y] = x + (y - x) / 2;
}
while (R.size() > 1) {
int x = R.back(); R.pop_back();
int y = R.back(); R.pop_back();
ans[x] = ans[y] = (m - x) + (x - y) / 2;
}
if (L.size() and R.size()) {
int x = L.back(); L.pop_back();
int y = R.back(); R.pop_back();
ans[x] = ans[y] = (x + (m - y) + m) / 2;
}
};
solve(v, 0), solve(v, 1);
for (auto [x, dir] : v) {
cout << (ans.count(x) ? ans[x] : -1) << ' ';
}
cout << "\n";
}
return 0;
}
D. Armchairs
题意
给出一个大小为 \(n\) ,由 \(0,1\) 构成的数组 \(a\) 。
把位于 \(j\) 的 \(1\) 移到位置 \(i\) 处的花费为 \(abs(i - j)\) ,一个位置只能放一个 \(1\) 。
问把所有 \(1\) 移动到初始为 \(0\) 处的最小花费。
题解
设 \(dp_{ij}\) 为前 \(i\) 个位置放前 \(j\) 个 \(1\) 的最小花费,则状态转移方程为:
\(dp_{ij} = min(dp_{i-1 j},dp_{i-1 j-1} + abs(i - pos_j))\) ,且后者仅当时当前位置初始为 \(0\) 才可取得。
Tips
不能贪心
8
0 0 1 1 0 1 1 0
代码
#include <bits/stdc++.h>
using namespace std;
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<int> pos;
for (int i = 0; i < n; i++) {
if (a[i] == 1) {
pos.push_back(i);
}
}
vector<vector<int>> dp(n + 1, vector<int> (pos.size() + 1, 1e9));
for (int i = 0; i <= n; i++) {
dp[i][0] = 0;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= (int)pos.size(); j++) {
dp[i][j] = min(dp[i - 1][j], (a[i - 1] == 0 ? dp[i - 1][j - 1] + abs(i - 1 - pos[j - 1]) : INT_MAX));
}
}
cout << dp[n][pos.size()] << "\n";
return 0;
}