AtCoder Beginner Contest 361
A - Insert (abc361 A)
题目大意
给定一个数组和数 ,将 插入第 个数之后,并输出新数组。
解题思路
用的直接 即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, k, x; cin >> n >> k >> x; vector<int> a(n); for (auto& i : a) cin >> i; a.insert(a.begin() + k, x); for (auto i : a) cout << i << ' '; cout << '\n'; return 0; }
B - Intesection of Cuboids (abc361 B)
题目大意
给定两个立方体,问是否相交。
解题思路
因为立方体都是平行坐标轴摆放的,若相交,则说明在各个维度上的线段都相交。(可以考虑二维的长方形)
因此判断三个维度的线段是否都相交即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int a, b, c, d, e, f; int g, h, i, j, k, l; cin >> a >> b >> c >> d >> e >> f; cin >> g >> h >> i >> j >> k >> l; auto overlap = [](int l1, int r1, int l2, int r2) { return max(l1, l2) < min(r1, r2); }; bool x = overlap(a, d, g, j); bool y = overlap(b, e, h, k); bool z = overlap(c, f, i, l); if (x && y && z) { cout << "Yes" << endl; } else { cout << "No" << endl; } return 0; }
C - Make Them Narrow (abc361 C)
题目大意
给定个数,删去其中的 个数,使其极差(最大值与最小值的差)最小。
解题思路
将从小到大排序,我删除其中的 个数,一定是从最小值和最大值开始删除,不会从中间删(这对极差不会有任何影响)。
决策就是我从最小值开始删多少个数,如果我选择删 个数,那么从最大值开始要删 个数。所有的 的情况的极差取个最小值即为答案。
而 的范围就是 ,直接枚举 即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, k; cin >> n >> k; vector<int> a(n); for (auto& x : a) cin >> x; sort(a.begin(), a.end()); int ans = 1e9 + 7; for (int i = 0; i <= k; i++) { ans = min(ans, a[n - (k - i) - 1] - a[i]); } cout << ans << '\n'; return 0; }
D - Go Stone Puzzle (abc361 D)
题目大意
个格子,其中前 个格子有石头,石头有黑有白。每次操作。
将相邻两个石头移动到无石头的位置,俩石头相对顺序不变。
给定初始局面和最终局面,问操作次数的最小值。
解题思路
注意到,局面数最多只有 。因此直接从初始局面进行 ,枚举操作,转移后续状态即可。
枚举操作即,先花 找到空位,然后花 枚举要移动的两个石头,移动后得到后继状态。转移复杂度即为。
由于数很小,状态记录可以直接用vector
,用map
记录抵达状态的操作次数,开销不会很大,不用二进制压缩,也方便写转移。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; string s, t; cin >> n >> s >> t; vector<int> st(n + 2, 2), ed(n + 2, 2); for (int i = 0; i < n; i++) { st[i] = s[i] == 'B'; ed[i] = t[i] == 'B'; } map<vector<int>, int> cnt; queue<vector<int>> q; q.push(st); cnt[st] = 0; while (!q.empty()) { auto u = q.front(); q.pop(); int d = cnt[u]; if (u == ed) { break; } int empty = find(u.begin(), u.end(), 2) - u.begin(); for (int i = 0; i < n + 1; ++i) { auto v = u; if (v[i] != 2 && v[i + 1] != 2) { swap(v[i], v[empty]); swap(v[i + 1], v[empty + 1]); if (!cnt.count(v)) { cnt[v] = d + 1; q.push(v); } } } } if (!cnt.count(ed)) { cnt[ed] = -1; } cout << cnt[ed] << '\n'; return 0; }
E - Tree and Hamilton Path 2 (abc361 E)
题目大意
给定一棵树,边有边权。问从一个点出发,访问完所有节点的最小路径长度。
解题思路
首先注意到,从一个点出发,最终停下来的点一定是叶子。
其次,考虑每条边访问的次数,会发现只有这条链上的边只访问了一次,其他边均访问两次。
因此最终的路径长度即为 ,即所有边权和的两倍
减去根到叶子的距离
。
最小化路径长度,即最大化根到叶子的距离
。
由于根不是指定的,因此要找的是树上两点的最长距离
。
这即为树的直径,两次 即可找出。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; vector<vector<array<int, 2>>> edge(n); LL sum = 0; for (int i = 0; i < n - 1; ++i) { int u, v, w; cin >> u >> v >> w; --u, --v; edge[u].push_back({v, w}); edge[v].push_back({u, w}); sum += w; } vector<LL> dis(n, 0); auto dfs = [&](auto&& dfs, int u, int fa) -> void { for (auto [v, w] : edge[u]) { if (v == fa) continue; dis[v] = dis[u] + w; dfs(dfs, v, u); } }; dfs(dfs, 0, 0); int l = max_element(dis.begin(), dis.end()) - dis.begin(); dis.assign(n, 0); dfs(dfs, l, l); LL max_dis = *max_element(dis.begin(), dis.end()); cout << sum * 2 - max_dis << endl; return 0; }
F - x = a^b (abc361 F)
题目大意
给定,求 ,满足存在,使得 。
。
解题思路
由于,因此 的范围就是 ,即,直接枚举的话是 。
注意到当时, ,因此先枚举 ,把的全部找出来,这里的时间复杂度为 ,全部存在vector
然后排序去重即可得到这部分的的数量。
然后考虑剩下的 的的数量。
一个比较浅显的想法,认为这部分的数量为。但容易发现会算重:如果,其中,那么 ,这个数其实是上面算过的(部分)。
因此要把重复的部分去掉,考虑怎样的是重复的,即存在,容易发现这个条件就是上面枚举计算的条件,即重复的都在vector
里出现过。
因此再减去vector
里出现过的的数即可,二分找到对应的下标相减即为重复的数量。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); LL n; cin >> n; int up = 1e6; LL upp = 1e18; vector<LL> s{1}; for (int i = 2; i <= up; i++) { __int128 x = 1ll * i * i; while (x <= n) { s.push_back(x); x *= i; } } sort(s.begin(), s.end()); s.erase(unique(s.begin(), s.end()), s.end()); LL ans = s.size(); int half = sqrt(n); if (half > up) { ans += half - up; auto r = upper_bound(s.begin(), s.end(), half) - s.begin(); auto l = upper_bound(s.begin(), s.end(), up) - s.begin(); ans -= r - l; } cout << ans << '\n'; return 0; }
G - Go Territory (abc361 G)
题目大意
二维平面,有障碍物,可以上下左右走。
问有多少个点,不可以走到。
解题思路
看的时候发觉很久以前做过的类似的题,基本做法一致。
首先看样例给的图
一个朴素的想法就是找到不在原点连通块
的点,这些点的个数和就是答案。
如果二维平面很小的话,可以对每个点进行,找到所有的连通块,然后累计非原点连通块的点,其值即为答案。
但是这里的平面大小有 ,不能。
由于每个连通块都是一个封闭的图形,我们要统计的就是这个图形的点数,类似面积,可以使用扫描线的方法。从下往上扫描每一行的线段,这些线段是由障碍物作分割,然后用并查集维护这些线段所属的连通块。
考虑第行,第 行的障碍物把该行分割成了 条线段,并且已经用并查集维护好了这些线段所属的连通块。然后考虑第 行,第 行的障碍物同样把该行分割成了 条线段,现在我们就需要将这两行线段合并
,得到第 行的每个线段所属的连通块是哪个。
合并即考虑上下两行的两个线段,如果它们是相交的,那么它们应属于同一个连通块,并查集合并起来。这是一个模拟的过程,有点小细节。
得到第 行的线段的连通块关系,继续合并第 行,依次往复,扫描整个平面。最后遍历所有连通块,把不在原点连通块的点
累加即为答案。
而对于该行没有障碍物的,则视为一条线段,如果有连续若干行无障碍物,这我们可以把这若干行压成一行,视为一条线段。最后扫描时按照障碍物的第一维排序扫描。
特殊处理无障碍物的情况。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; struct segg { int l, r, id; }; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, h = 2e5 + 8, w = 2e5 + 8; // 大平面[1, h] * [1, w] cin >> n; vector<pair<int, int>> pos(n); for (auto& i : pos) { cin >> i.first >> i.second; i.first += 2; i.second += 2; // 原点视为(1, 1) } sort(pos.begin(), pos.end()); int cur = 0; vector<int> f; vector<LL> cnt; array<vector<segg>, 2> seg; auto findfa = [&](auto& findfa, int x) -> int { return f[x] == x ? x : f[x] = findfa(findfa, f[x]); }; auto crossover = [&](segg& a, segg& b) { return a.r >= b.l && a.l <= b.r; }; auto add_seg = [&](int l, int r, LL cc) { int id = f.size(); f.push_back(id); cnt.push_back(cc); seg[cur].push_back({l, r, id}); }; auto unionn = [&]() { // 将上下两行线段合并 auto& last_seg = seg[cur ^ 1]; auto& cur_seg = seg[cur]; auto last_pt = last_seg.begin(); for (auto& i : cur_seg) { while (true) { if (last_pt != last_seg.end() && crossover(i, *last_pt)) { // 线段相交 int fa = findfa(findfa, i.id); int fb = findfa(findfa, last_pt->id); if (fa != fb) { f[fa] = fb; cnt[fb] += cnt[fa]; } last_pt = next(last_pt); } else if (last_pt == last_seg.end() || last_pt->l > i.r) { // 形如 cur_pt ..... last_pt if (last_pt != last_seg.begin()) last_pt = prev(last_pt); break; } else // 形如 last_pt ..... cur_pt last_pt = next(last_pt); } } }; auto skip_line = [&](int l, int r) { // 空行[l, r],无障碍物 if (l > r) return; cur ^= 1; seg[cur].clear(); add_seg(1, w, (r - l + 1ll) * w); unionn(); }; auto solve_pos = [&](int l, int r) { // 处理该行的所有障碍物pos[l..r] if (l > r) return; cur ^= 1; seg[cur].clear(); int la = 0; for (int i = l; i <= r; ++i) { if (pos[i].second - la > 1) { add_seg(la + 1, pos[i].second - 1, pos[i].second - la - 1); } la = pos[i].second; } if (w > la) { add_seg(la + 1, w, w - la); } unionn(); }; LL ans = 0; if (n == 0) { ans = 0; } else { skip_line(1, pos[0].first - 1); int la = 0; for (int i = 1; i < n; ++i) { if (pos[i].first != pos[la].first) { solve_pos(la, i - 1); // 处理同行的所有障碍物 skip_line(pos[la].first + 1, pos[i].first - 1); // 处理空行 la = i; } } solve_pos(la, n - 1); skip_line(pos[la].first + 1, h); // 最顶部还有空行 int origin = findfa(findfa, seg[cur].front().id); // 起点的连通块编号 for (int i = 0; i < f.size(); ++i) { if (findfa(findfa, i) == i && i != origin) { ans += cnt[i]; } } } cout << ans << '\n'; return 0; }
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/18288078
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步