AtCoder Beginner Contest 221【A - G】
比赛链接: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