CF Educational Round4 个人题解
CF Educational Round4 个人题解

A. The Text Splitting
题意
给定一个长度为 \(n\) 的字符串 \(s\) 和两个整数 \(p, \ q\)
问能否将这个字符串分解成若干个长度为 \(p\) 和 \(q\) 的字符串,如果可以的话输出分解成的字符串数量和分解成的字符出啊,否则输出 -1
思路
因为\(n, \ p, \ q\) 都小于 \(100\),所以显然直接暴力找方程 \(ap + bq = n\) 的整数解即可
但是我没有看数据范围,所以这里用了拓展欧几里得求解 QwQ
复杂度 \(O(logn)\)
代码
// A. The Text Splitting
// 1000 ms
// 256 MB
// https://codeforces.com/contest/612/problem/A
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
auto extgcd = [](int a, int b, int& x, int& y) {
x = 1, y = 0;
int x1 = 0, y1 = 1;
int a1 = a, b1 = b;
while (b1) {
int q = a1 / b1;
x xor_eq x1 xor_eq x xor_eq x1;
x1 = x1 - q * x;
y xor_eq y1 xor_eq y xor_eq y1;
y1 = y1 - q * y;
a1 xor_eq b1 xor_eq a1 xor_eq b1;
b1 = b1 % a1;
}
return a1;
};
int n, p, q;
std::string s;
std::cin >> n >> p >> q >> s;
int x, y;
int d = extgcd(p, q, x, y);
if (n % d != 0) {
std::cout << -1 << '\n';
return;
}
int mul = n / d;
x *= mul;
y *= mul;
int k = q / d;
int t;
if (x < 0)
t = (-x + k - 1) / k;
else
t = -x / k;
x += k * t;
y -= (p / d) * t;
if (x < 0 or y < 0) {
std::cout << -1 << '\n';
return;
}
std::cout << x + y << '\n';
int idx = 0;
for (int i = 0; i < x; ++i) {
std::cout << s.substr(idx, p) << '\n';
idx += p;
}
for (int i = 0; i < y; ++i) {
std::cout << s.substr(idx, q) << '\n';
idx += q;
}
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
#ifdef LOCAL
int __edt = clock();
std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
return 0;
}
B. HDD is Outdated Technology
题意(形式化)
给定一个长度为 \(n\) 的排列 \(f\), 第 \(i\) 个数在第 \(p_i\) 个位置出现,求 \(\sum_{i = 1}^{n - 1} \ |p_{i + 1} - p_i|\)
思路
温馨提示: 本题需要开龙龙(我因为这个挂了一发)
根据形式化题意模拟就对了
复杂度 \(O(n)\)
代码
// B. HDD is Outdated Technology
// 1000 ms
// 256 MB
// https://codeforces.com/contest/612/problem/B
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
#define int i64
int n;
std::cin >> n;
std::vector<int> a(n + 1, 0);
std::vector<int> idx(n + 1, 0);
for (int i = 1; i <= n; ++i) {
std::cin >> a[i];
idx[a[i]] = i;
}
int ans = 0;
for (int i = 1; i < n; ++i)
ans += std::abs(idx[i + 1] - idx[i]);
std::cout << ans << '\n';
#undef int
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
#ifdef LOCAL
int __edt = clock();
std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
return 0;
}
C. Replace To Make Regular Bracket Sequence
题意
给定一个由 \(<, \ >, \ [, \ ], \ \{, \ \}, \ (, \ )\) 组成的括号序列 \(s\)
现在可以把同方向的括号任意替换,如你可以把 \(<\) 替换为 \((\), 现在问你至少替换多少次可以将这个序列变为正则的,无解输出 "Impossible"
思路
前置知识
对于括号序列类题目,我们常见的做法之一是把左括号的权值赋为 \(1\), 右括号的权值赋为 \(-1\),将整个括号序列转化成一个权值前缀数组
如果这个数组没有任何一个位置的值 \(< \ 0\) 且这个数组的最后一个值 $ = \ 0$ 则这是一个正则括号序列
无解情况
对于本题的所有操作情况,我们无法改变这个位置上的权值,所以如果原序列不满足一个和正则括号序列的权值条件,则我们认定它无解
最优替换
贪心的想,一个正则括号串的括号的括号匹配的二元组是固定的,所以一个应当匹配的括号二元组最多改一个让这一组括号匹配
综上,我们可以写出这题的代码了
温馨提示: 这道题目括号种类较为多,建议多用函数封装一下
时间复杂度 \(O(n)\)
代码
// C. Replace To Make Regular Bracket Sequence
// 1000 ms
// 256 MB
// https://codeforces.com/contest/612/problem/C
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
std::string s;
std::cin >> s;
int n = (int)s.size();
auto is_right = [&](int pos) -> bool {
return (s[pos - 1] == '<' or s[pos - 1] == '(' or s[pos - 1] == '{' or
s[pos - 1] == '[');
};
auto match = [](char c) -> char {
if (c == '(')
return ')';
else if (c == '[')
return ']';
else if (c == '{')
return '}';
else if (c == '<')
return '>';
};
std::vector<int> val(n + 1, 0);
for (int i = 1; i <= n; ++i)
val[i] = val[i - 1] + (is_right(i) ? 1 : -1);
if (val[n] != 0 or *std::min_element(val.begin() + 1, val.end()) < 0) {
std::cout << "Impossible" << '\n';
return;
}
int ans = 0;
std::stack<char> st;
for (int i = 1; i <= n; ++i) {
// if (not st.empty())
// std::cerr << st.top() << '\n';
if (is_right(i))
st.emplace(s[i - 1]);
else {
if (s[i - 1] != match(st.top()))
++ans;
st.pop();
}
}
std::cout << ans << '\n';
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
#ifdef LOCAL
int __edt = clock();
std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
return 0;
}
D. The Union of k-Segments
题意
给定 \(n\) 条数轴上的线段,求至少被覆盖 \(k\) 次的点或者线段
思路1(离散化线段树)
这个做法其实比较不聪明,属于又累又慢又大(参见 D 题提交的第一发)
显然我们可以将原线段端点离散化,然后用线段树区间加维护每个位置被覆盖的次数’
最后遍历整颗线段树找到区间并合并相邻区间
时间复杂度 \(O(n \ logn)\)
空间复杂度 \(O(n)\)
代码1(离散化线段树)
// D. The Union of k-Segments
// 4000 ms
// 256 MB
// https://codeforces.com/contest/612/problem/D
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
struct SegTree {
std::vector<int> lazy;
int sz;
SegTree(int n) {
sz = n << 2 | 1;
lazy.assign(sz, 0);
}
void upd(int id, int l, int r, int ql, int qr) {
if (ql <= l and r <= qr) {
++lazy[id];
return;
}
int mid = (l + r) >> 1;
if (ql <= mid)
upd(id << 1, l, mid, ql, qr);
if (qr > mid)
upd(id << 1 | 1, mid + 1, r, ql, qr);
}
void dfs(int id,
int l,
int r,
int acc,
int& blk,
int& st,
std::vector<std::pair<int, int>>& res,
const std::vector<int>& xs,
int k) {
acc += lazy[id];
if (l == r) {
if (acc >= k) {
if (blk == 0) {
blk = 1;
st = l;
}
} else {
if (blk) {
blk = 0;
int ed = l - 1;
res.emplace_back(xs[st / 2], xs[ed / 2]);
}
}
return;
}
int mid = (l + r) >> 1;
dfs(id << 1, l, mid, acc, blk, st, res, xs, k);
dfs(id << 1 | 1, mid + 1, r, acc, blk, st, res, xs, k);
}
};
int n, k;
std::cin >> n >> k;
std::vector<int> a(n), b(n);
std::vector<int> segs;
for (int i = 0; i < n; ++i) {
std::cin >> a[i] >> b[i];
segs.emplace_back(a[i]);
segs.emplace_back(b[i]);
}
std::sort(segs.begin(), segs.end());
segs.erase(std::unique(segs.begin(), segs.end()), segs.end());
int m = segs.size();
int N = 2 * m - 1;
SegTree seg(N);
for (int i = 0; i < n; ++i) {
int p1 =
std::lower_bound(segs.begin(), segs.end(), a[i]) - segs.begin();
int p2 =
std::lower_bound(segs.begin(), segs.end(), b[i]) - segs.begin();
int L = 2 * p1;
int R = 2 * p2;
seg.upd(1, 0, N - 1, L, R);
}
int blk = 0, st = -1;
std::vector<std::pair<int, int>> ans;
seg.dfs(1, 0, N - 1, 0, blk, st, ans, segs, k);
if (blk) {
int ed = N - 1;
ans.emplace_back(segs[st / 2], segs[ed / 2]);
}
std::cout << ans.size() << '\n';
for (const auto& [x, y] : ans)
std::cout << x << ' ' << y << '\n';
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
#ifdef LOCAL
int __edt = clock();
std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
return 0;
}
思路2(扫描线)
杂谈: 还是练少了,这么显然的一个做法竟然在我调了快半个小时线段树后才想到,但既然想到了就顺手写了
这是一个一维的扫描线,记录当前位置被 \(cnt\) 条线段覆盖
\(n\) 条线段的 \(2 \times n\) 个端点会组成 \(2 \times n - 1\) 个线段
每次遇到一个线段的起始端点我们就将 \(cnt + 1\),遇到一个线段的终止端点就将 \(cnt - 1\)
将线段按左端点排序之后可以避免离散化的维护出这些线段被覆盖的次数
最后统计一下答案,合并相邻线段即可
时间复杂度: \(O(n \ logn)\)
空间复杂度: \(O(n)\)
代码
// D. The Union of k-Segments
// 4000 ms
// 256 MB
// https://codeforces.com/contest/612/problem/D
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<std::pair<int, int>> ev;
for (int i = 0; i < n; ++i) {
int l, r;
std::cin >> l >> r;
ev.emplace_back(l, 1);
ev.emplace_back(r, -1);
}
std::sort(ev.begin(), ev.end(), [](auto a, auto b) {
if (a.first != b.first)
return a.first < b.first;
return a.second > b.second;
});
std::vector<std::pair<int, int>> ans;
int cnt = 0;
int lst = 0;
bool in = false;
for (const auto& [pos, t] : ev) {
if (t == 1) {
++cnt;
if (cnt == k) {
lst = pos;
in = true;
}
} else {
if (cnt == k) {
if (in) {
ans.emplace_back(lst, pos);
in = false;
}
}
--cnt;
}
}
std::cout << ans.size() << '\n';
for (const auto& [l, r] : ans)
std::cout << l << ' ' << r << '\n';
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
#ifdef LOCAL
int __edt = clock();
std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
return 0;
}
E. Square Root of Permutation
题意
对于一个排列 \(p\),我们定义 \(p^2\) 为另一个排列 \(q\),满足 \(q_i = p_{p_i}\)
现在给出一个排列 \(p\),找到任意一个排列 \(q\) 使得 \(q^2 = p\),如果不存在输出 \(-1\)
思路
前置知识 群的置换
这是一个置换群,所以它可以分解成若干个置换环,我们分析这样的环
令这个环长 \(l\)
若 \(2 \ | \ l\)
则它会变为 \(s_1 \to s_3 \to s_5 \to ... \to s_l \to s_2 \to s_4 \to ... \to s_{l - 1} \to s_1\)
若 \(2 \ \nmid l\) 则这个环会裂成两个环:
环一: \(s_1 \to s_3 \to s_5 \to ... \to s_{l - 1} \to s_1\)
环二:\(s_2 \to s_4 \to s_6 \to ... \to s_l \to s_2\)
所以我们得到一个结论:奇环置换后的个数大小不变,偶环会得到两个大小相等的环
所以我们先求出排列 \(p\) 的置换环,有解当且仅当偶环数量为偶数且大小两两对应相等;偶环数量为偶数且大小两两对应相等当且仅当有解
即 \(有解 \Leftrightarrow 偶环数量为偶数且大小两两对应相等\)
接下来再次分奇偶复原
对于一个奇环,我们根据它的置换方式可以得到 \(q_{p_i} = p_{(i + \lfloor{\frac{len}{2}}\rfloor) \ mod \ len}\)
对于偶环,我们要找到对应的两个偶环然后拼起来,这里可以以长度为关键字排序若干偶环,这样的话相邻两个就为对应偶环了
若这两个环为 \(a, b\),则有
时间复杂度 \(O(n \ logn)\)
代码
// E. Square Root of Permutation
// 2000 ms
// 256 MB
// https://codeforces.com/contest/612/problem/E
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
int n;
std::cin >> n;
std::vector<int> p(n + 1, 0);
for (int i = 1; i <= n; ++i)
std::cin >> p[i];
std::vector<int> ans(n + 1, 0);
std::vector<bool> seen(n + 1, false);
std::vector<std::vector<int>> evLoops;
for (int i = 1; i <= n; ++i) {
if (not seen[i]) {
std::vector<int> cyc;
for (int u = i; not seen[u]; u = p[u]) {
seen[u] = true;
cyc.emplace_back(u);
}
int sz = (int)cyc.size();
if (sz & 1) {
int shift = (sz + 1) / 2;
for (int j = 0; j < sz; ++j)
ans[cyc[j]] = cyc[(j + shift) % sz];
} else {
evLoops.emplace_back(std::move(cyc));
}
}
}
int cnt = (int)evLoops.size();
if (cnt & 1) {
std::cout << -1 << '\n';
return;
}
std::sort(evLoops.begin(), evLoops.end(),
[](const auto& A, const auto& B) { return A.size() < B.size(); });
for (int i = 0; i < cnt; i += 2) {
if (evLoops[i].size() != evLoops[i + 1].size()) {
std::cout << -1 << '\n';
return;
}
int len = (int)evLoops[i].size();
auto& cycA = evLoops[i];
auto& cycB = evLoops[i + 1];
for (int j = 0; j < len; ++j) {
ans[cycA[j]] = cycB[j];
ans[cycB[j]] = cycA[(j + 1) % len];
}
}
for (int i = 1; i <= n; ++i)
std::cout << ans[i] << " \n"[i == n];
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
#ifdef LOCAL
int __edt = clock();
std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
return 0;
}
F. Simba on the Circle
严肃加入题单

浙公网安备 33010602011771号