AtCoder Beginner Contest 361
A - Insert (abc361 A)
题目大意
给定一个数组\(a\)和数 \(k,x\),将 \(x\)插入第 \(k\)个数之后,并输出新数组。
解题思路
用\(vector\)的直接 \(insert\)即可。
神奇的代码
#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)
题目大意
给定\(n\)个数\(a_i\),删去其中的 \(k\)个数,使其极差(最大值与最小值的差)最小。
解题思路
将\(a_i\)从小到大排序,我删除其中的 \(k\)个数,一定是从最小值和最大值开始删除,不会从中间删(这对极差不会有任何影响)。
决策就是我从最小值开始删多少个数,如果我选择删 \(x\)个数,那么从最大值开始要删 \(k-x\)个数。所有的 \(x\)的情况的极差取个最小值即为答案。
而 \(x\)的范围就是 \(O(n)\),直接枚举 \(x\)即可。
神奇的代码
#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)
题目大意
\(n+2\)个格子,其中前 \(n\)个格子有石头,石头有黑有白。每次操作。
将相邻两个石头移动到无石头的位置,俩石头相对顺序不变。
给定初始局面和最终局面,问操作次数的最小值。
解题思路
注意到\(n \leq 14\),局面数最多只有 \(C_{14}^{7} < 1e5\)。因此直接从初始局面进行 \(BFS\),枚举操作,转移后续状态即可。
枚举操作即,先花 \(O(n)\)找到空位,然后花 \(O(n)\)枚举要移动的两个石头,移动后得到后继状态。转移复杂度即为\(O(n)\)。
由于数很小,状态记录可以直接用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)
题目大意
给定一棵树,边有边权。问从一个点出发,访问完所有节点的最小路径长度。
解题思路
首先注意到,从一个点出发\(st\),最终停下来的点一定是叶子\(ed\)。
其次,考虑每条边访问的次数,会发现只有\(st \to ed\)这条链上的边只访问了一次,其他边均访问两次。
因此最终的路径长度即为 \(2 \sum e_i - cost(st \to ed)\),即所有边权和的两倍
减去根到叶子的距离
。
最小化路径长度,即最大化根到叶子的距离
。
由于根不是指定的,因此要找的是树上两点的最长距离
。
这即为树的直径,两次 \(DFS\)即可找出。
神奇的代码
#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)
题目大意
给定\(n\),求 \(x \in [1,n]\) ,满足存在\(a,b(b \geq 2)\),使得 \(x = a^b\)。
\(n \leq 10^{18}\)。
解题思路
由于\(b \geq 2\),因此 \(a\)的范围就是 \([1, \sqrt{n}]\),即\(a \in [1, 10^9]\),直接枚举\(a,b\)的话是 \(O(10^9 \log 10^9)\)。
注意到当\(b \geq 3\)时,\(a \in [1, 10^6]\) ,因此先枚举\(a \in [1, 10^6]\) ,把\(a \leq 10^6, b \geq 2\)的\(x\)全部找出来,这里的时间复杂度为 \(O(10^6 \log 10^6)\),全部存在vector
然后排序去重即可得到这部分的\(x\)的数量。
然后考虑剩下的 \(10^6 < a \leq 10^9, b = 2\)的\(x\)的数量。
一个比较浅显的想法,认为这部分的数量为\(\lfloor \sqrt{n} \rfloor - 10^6\)。但容易发现会算重:如果\(a = c^2\),其中\(c \leq 10^6\),那么 \(a^2 = c^4\) ,这个数其实是上面算过的(\(a \leq 10^6, b \geq 2\)部分)。
因此要把重复的部分去掉,考虑怎样的\(a\)是重复的,即存在\(b \geq 2, a = c^b(c \leq 10^6)\),容易发现这个条件就是上面枚举计算的条件,即重复的都在vector
里出现过。
因此\(\lfloor \sqrt{n} \rfloor - 10^6\)再减去vector
里出现过的\((10^6, \lfloor \sqrt{n} \rfloor]\)的数即可,二分找到对应的下标相减即为重复的数量。
神奇的代码
#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)
题目大意
二维平面,有障碍物,可以上下左右走。
问有多少个点,不可以走到\((-1, -1)\)。
解题思路
看的时候发觉很久以前做过的类似的题,基本做法一致。
首先看样例给的图
一个朴素的想法就是找到不在原点连通块
的点,这些点的个数和就是答案。
如果二维平面很小的话,可以对每个点进行\(BFS\),找到所有的连通块,然后累计非原点连通块的点,其值即为答案。
但是这里的平面大小有 \(10^5 \times 10^5\),不能\(BFS\)。
由于每个连通块都是一个封闭的图形,我们要统计的就是这个图形的点数,类似面积,可以使用扫描线的方法。从下往上扫描每一行的线段,这些线段是由障碍物作分割,然后用并查集维护这些线段所属的连通块。
考虑第\(i\)行,第 \(i\)行的障碍物把该行分割成了 \(x\)条线段,并且已经用并查集维护好了这些线段所属的连通块。然后考虑第 \(i+1\)行,第 \(i+1\)行的障碍物同样把该行分割成了 \(y\)条线段,现在我们就需要将这两行线段合并
,得到第 \(i+1\)行的每个线段所属的连通块是哪个。
合并即考虑上下两行的两个线段,如果它们是相交的,那么它们应属于同一个连通块,并查集合并起来。这是一个模拟的过程,有点小细节。
得到第 \(i+1\)行的线段的连通块关系,继续合并第 \(i+2\)行,依次往复,扫描整个平面。最后遍历所有连通块,把不在原点连通块的点
累加即为答案。
而对于该行没有障碍物的,则视为一条线段,如果有连续若干行无障碍物,这我们可以把这若干行压成一行,视为一条线段。最后扫描时按照障碍物的第一维排序扫描。
特殊处理无障碍物的情况。
神奇的代码
#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;
}