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;
}