比賽鏈接:https://atcoder.jp/contests/abc221/tasks
A - Seismic magnitude scales
題意
給出兩個正整數 \(a, b\) ,計算 \(32^{a - b}\) 。
- \(3 \le b \le a \le 9\)
題解
\(32^{a - b} = 2 ^ {5(a - b)}\) 。
代碼
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int a, b;
cin >> a >> b;
cout << (1 << (5 * (a - b))) << "\n";
return 0;
}
B - typo
題意
給出兩個字符串 \(s, t\) ,判斷能否交換 \(s\) 中至多一對相鄰字符,使得 \(s\) 與 \(t\) 相同。
- \(2 \le |s| = |t| \le 100\)
題解
枚舉交換的位置,同時判斷前后的 \(s, t\) 是否相同即可。
代碼
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s, t;
cin >> s >> t;
int n = s.size();
auto query = [&](int l, int r) {
for (int i = l; i <= r; i++) {
if (s[i] != t[i]) {
return false;
}
}
return true;
};
bool ok = (s == t);
for (int i = 0; i + 1 < n; i++) {
if (s[i] == t[i + 1] and s[i + 1] == t[i] and query(0, i - 1) and query(i + 2, n - 1)) {
ok = true;
}
}
cout << (ok ? "Yes" : "No") << "\n";
return 0;
}
C - Select Mul
題意
給出一個正整數 \(n\) ,將其分為兩個非空子序列,子序列可以重新排列。
計算在所有可能的情況中,兩個子序列之積的最大值。
- \(1 \le n \le 10^9\)
題解
枚舉所有分類情況即可,時間復雜度約為 \(2^{len(n)}\) 。
代碼
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s;
cin >> s;
int n = s.size();
int ans = 0;
for (int i = 0; i < (1 << n); i++) {
string a, b;
for (int j = 0; j < n; j++) {
(i & (1 << j) ? a : b) += s[j];
}
if (a.empty() or b.empty()) {
continue;
}
sort(a.begin(), a.end(), greater<>());
sort(b.begin(), b.end(), greater<>());
ans = max(ans, stoi(a) * stoi(b));
}
cout << ans << "\n";
return 0;
}
D - Online games
題意
給出 \(n\) 個區間起點 \(a_i\) 及區間長度 \(b_i\) ,計算有多少個數恰好被 \(1, \dots, n\) 個區間覆蓋。
- \(1 \le n \le 2 \times 10^5\)
- \(1 \le a_i, b_i \le 10^9\)
題解一
將區間端點離散化后差分進行區間加和,然后遍歷一次所有點即可。
代碼
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<pair<int, int>> seg(n);
vector<int> disc;
for (int i = 0; i < n; i++) {
int a, b;
cin >> a >> b;
seg[i] = {a, a + b - 1};
for (int j = -1; j <= 1; j++) {
disc.push_back(a + j);
disc.push_back(a + b - 1 + j);
}
}
sort(disc.begin(), disc.end());
disc.resize(unique(disc.begin(), disc.end()) - disc.begin());
const int N = disc.size();
vector<int> cnt(N);
map<int, int> mp;
for (auto [l, r] : seg) {
int nl = lower_bound(disc.begin(), disc.end(), l) - disc.begin();
int nr = lower_bound(disc.begin(), disc.end(), r) - disc.begin();
mp[nl] = l, mp[nr] = r;
++cnt[nl], --cnt[nr + 1];
}
for (int i = 1; i < N; i++) {
cnt[i] += cnt[i - 1];
mp[i] = max(mp[i], mp[i - 1] + 1);
}
vector<int> ans(n + 1);
for (int i = 1; i < N; i++) {
ans[cnt[i]] += mp[i + 1] - mp[i];
}
for (int i = 1; i <= n; i++) {
cout << ans[i] << " \n"[i == n];
}
return 0;
}
題解二
比較巧妙的一種離散化構造方法。
代碼
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<pair<int, int>> seg;
for (int i = 0; i < n; i++) {
int a, b;
cin >> a >> b;
seg.emplace_back(a, 1);
seg.emplace_back(a + b, -1);
}
sort(seg.begin(), seg.end());
vector<int> ans(n + 1);
int cnt = 0;
for (int i = 0; i + 1 < 2 * n; i++) {
cnt += seg[i].second;
ans[cnt] += seg[i + 1].first - seg[i].first;
}
for (int i = 1; i <= n; i++) {
cout << ans[i] << " \n"[i == n];
}
return 0;
}
E - LEQ
題意
給出一個長為 \(n\) 的整數序列 \(a\) ,計算有多少首部小於等於尾部,長度至少為 \(2\) 的子序列,對 \(998244353\) 取模。
- \(2 \le n \le 3 \times 10^5\)
- \(1 \le a_i \le 10^9\)
題解
對於 \(a_j\) 來說,以它為尾部滿足條件的子序列個數為 \(\sum \limits _{i \lt j, a_i \le a_j} 2^{j - i - 1}\) ,即 \(2 ^ j \times \sum \limits _{i \lt j, a_i \le a_j} \frac{1}{2^{i + 1}}\) 。
類似權值樹狀數組的思想,以對之后的數的貢獻 \(\frac{1}{2^{i + 1}}\) 對 \(a_i\) 進行更新即可。
代碼
#include <bits/stdc++.h>
using namespace std;
constexpr int MOD = 998244353;
int norm(int x) { if (x < 0) { x += MOD; } if (x >= MOD) { x -= MOD; } return x; }
template<class T> T binpow(T a, int b) { T res = 1; for (; b; b /= 2, a *= a) { if (b % 2) { res *= a; } } return res; }
struct Z {
int x;
Z(int x = 0) : x(norm(x)) {}
int val() const { return x; }
Z operator-() const { return Z(norm(MOD - x)); }
Z inv() const { assert(x != 0); return binpow(*this, MOD - 2); }
Z &operator*=(const Z &rhs) { x = 1LL * x * rhs.x % MOD; return *this; }
Z &operator+=(const Z &rhs) { x = norm(x + rhs.x); return *this; }
Z &operator-=(const Z &rhs) { x = norm(x - rhs.x); return *this; }
Z &operator/=(const Z &rhs) { return *this *= rhs.inv(); }
friend Z operator*(const Z &lhs, const Z &rhs) { Z res = lhs; res *= rhs; return res; }
friend Z operator+(const Z &lhs, const Z &rhs) { Z res = lhs; res += rhs; return res; }
friend Z operator-(const Z &lhs, const Z &rhs) { Z res = lhs; res -= rhs; return res; }
friend Z operator/(const Z &lhs, const Z &rhs) { Z res = lhs; res /= rhs; return res; }
};
struct Fenwick_tree {
vector<Z> bit;
Fenwick_tree(int n) : bit(n + 1) {}
void update(int pos, Z val) {
for (int i = pos; i < (int)bit.size(); i += i & (-i)) {
bit[i] += val;
}
}
Z query(int pos) {
Z res = 0;
for (int i = pos; i >= 1; i -= i & (-i)) {
res += bit[i];
}
return res;
}
};
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> b(a);
sort(b.begin(), b.end());
b.resize(unique(b.begin(), b.end()) - b.begin());
for (auto& i : a) {
i = lower_bound(b.begin(), b.end(), i) - b.begin() + 1;
}
Z ans = 0;
Fenwick_tree F(n);
for (int i = 0; i < n; i++) {
ans += F.query(a[i]) * binpow(Z(2), i);
F.update(a[i], binpow(Z(2), i + 1).inv());
}
cout << ans.val() << "\n";
return 0;
}
F - Diameter set
題意
給出一棵大小為 \(n\) 的樹,計算有多少種染色方案(至少染 \(2\) 個結點),使得任意染色結點之間的距離均為樹的直徑。
- \(2 \le n \le 2 \times 10^5\)
題解
將樹從任意一條直徑中間切開,根據直徑長度的奇偶性分為兩種情況
- 奇數:移去中邊
- 偶數:移去與中點相連的邊
此時每棵子樹內的結點與切開處的根節點距離至多為 \(\lfloor \frac{diam - 1}{2} \rfloor\) ,即一棵子樹內最多只可能染一個結點。
設每棵子樹內與切開處的根節點距離為 \(\lfloor \frac{diam - 1}{2} \rfloor\) 的結點個數為 \(x_i\) , 答案即 \(\prod \limits (x_i + 1) - \sum \limits x_i - 1\) 。
含義為從所有染色情況中減去只染一個或不染的情況。
代碼
#include <bits/stdc++.h>
using namespace std;
constexpr int MOD = 998244353;
int norm(int x) { if (x < 0) { x += MOD; } if (x >= MOD) { x -= MOD; } return x; }
template<class T> T binpow(T a, int b) { T res = 1; for (; b; b /= 2, a *= a) { if (b % 2) { res *= a; } } return res; }
struct Z {
int x;
Z(int x = 0) : x(norm(x)) {}
int val() const { return x; }
Z operator-() const { return Z(norm(MOD - x)); }
Z inv() const { assert(x != 0); return binpow(*this, MOD - 2); }
Z &operator*=(const Z &rhs) { x = 1LL * x * rhs.x % MOD; return *this; }
Z &operator+=(const Z &rhs) { x = norm(x + rhs.x); return *this; }
Z &operator-=(const Z &rhs) { x = norm(x - rhs.x); return *this; }
Z &operator/=(const Z &rhs) { return *this *= rhs.inv(); }
friend Z operator*(const Z &lhs, const Z &rhs) { Z res = lhs; res *= rhs; return res; }
friend Z operator+(const Z &lhs, const Z &rhs) { Z res = lhs; res += rhs; return res; }
friend Z operator-(const Z &lhs, const Z &rhs) { Z res = lhs; res -= rhs; return res; }
friend Z operator/(const Z &lhs, const Z &rhs) { Z res = lhs; res /= rhs; return res; }
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<set<int>> G(n);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
--u, --v;
G[u].insert(v);
G[v].insert(u);
}
vector<int> dis(n), fa(n);
function<void(int, int)> dfs1 = [&](int u, int p) {
fa[u] = (p == -1 ? -1 : p);
dis[u] = (p == -1 ? 0 : dis[p] + 1);
for (auto v : G[u]) {
if (v != p) {
dfs1(v, u);
}
}
};
dfs1(0, -1);
int ua = max_element(dis.begin(), dis.end()) - dis.begin();
dfs1(ua, -1);
int ub = max_element(dis.begin(), dis.end()) - dis.begin();
vector<int> path;
for (int u = ub; u != -1; u = fa[u]) {
path.push_back(u);
}
reverse(path.begin(), path.end());
vector<int> root;
int diam = dis[ub];
if (diam & 1) {
int u = path[diam / 2], v = path[diam / 2 + 1];
G[u].erase(v);
G[v].erase(u);
root.push_back(u);
root.push_back(v);
} else {
int u = path[diam / 2];
root.assign(G[u].begin(), G[u].end());
for (auto v : G[u]) {
G[v].erase(u);
}
G[u].clear();
}
vector<int> v;
int cnt = 0;
function<void(int, int, int)> dfs2 = [&](int u, int p, int d) {
if (d == (diam - 1) / 2) {
++cnt;
}
for (auto v : G[u]) {
if (v != p) {
dfs2(v, u, d + 1);
}
}
};
for (auto u : root) {
cnt = 0;
dfs2(u, -1, 0);
v.push_back(cnt);
}
Z ans = 1;
for (auto x : v) {
ans *= (x + 1);
}
for (auto x : v) {
ans -= x;
}
ans -= 1;
cout << ans.val() << "\n";
return 0;
}
G - Jumping sequence
題意
在一個平面內可以上下左右移動,初始時位於 \((0, 0)\) 處,給出每步移動的距離 \(d_i\) ,判斷能否恰好 \(n\) 步后到達 \((a, b)\) 處,如果可以,輸出一種方案。
- \(1 \le n \le 2000\)
- \(|a|,|b| \le 3.6 \times 10^6\)
- \(1 \le d_i \le 1800\)
- 輸入均為整數
題解
比較巧妙的一種構造方案。
將原 \((x, y)\) 坐標系逆時針旋轉 45° 映射到 \((x + y, x - y)\) 坐標系中,此時上下左右移動的四種情況分別映射到 \((\pm d_i, \pm d_i)\) 。
然后問題轉化為尋找一種對 \(s_i, s'_i\) 的 \(+1, -1\) 賦值方案使得:
兩邊加上 \(s = \sum \limits _{i = 1} ^{n} d_i\) ,此時問題轉化為了尋找一種對 \(t_i, t'_i\) 的 \(0, 1\) 賦值方案使得:
使用 bitset 進行狀壓枚舉所有賦值方案即可。
因為轉換后坐標系的特殊性,在回溯后輸出方案時每一步需同時考慮 \(t_i, t'_i\) 的賦值情況。
代碼
#include <bits/stdc++.h>
using namespace std;
constexpr int M = 2000 * 1800 + 10;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, a, b;
cin >> n >> a >> b;
vector<int> d(n);
int s = 0;
for (int i = 0; i < n; i++) {
cin >> d[i];
s += d[i];
}
bool ok = true;
for (auto i : {a + b, a - b}) {
if ((s + i) % 2 != 0 or (s + i) / 2 < 0 or (s + i) / 2 > s) {
ok = false;
}
}
if (not ok) {
cout << "No" << "\n";
return 0;
}
int x = (s + a + b) / 2, y = (s + a - b) / 2;
vector<bitset<M>> bit(n + 1);
bit[0][0] = 1;
for (int i = 0; i < n; i++) {
bit[i + 1] = bit[i] | (bit[i] << d[i]);
}
if (not bit[n][x] or not bit[n][y]) {
cout << "No" << "\n";
return 0;
}
vector<bool> addx(n), addy(n);
for (int i = n - 1; i >= 0; i--) {
if (not bit[i][x]) {
x -= d[i];
addx[i] = true;
}
if (not bit[i][y]) {
y -= d[i];
addy[i] = true;
}
}
cout << "Yes" << "\n";
for (int i = 0; i < n; i++) {
cout << (addx[i] ? (addy[i] ? 'R' : 'U') : (addy[i] ? 'D' : 'L'));
}
cout << "\n";
return 0;
}
參考
https://atcoder.jp/contests/abc221/editorial/2733
https://atcoder.jp/contests/abc221/editorial/2732
