ABC453 解题记录
目录
- [ABC453 解题记录](#ABC453 解题记录)
- [A - Trimo](#A - Trimo)
- [B - Sensor Data Logging](#B - Sensor Data Logging)
- [C - Sneaking Glances](#C - Sneaking Glances)
- [D - Go Straight](#D - Go Straight)
- [E - Team Division](#E - Team Division)
- [G - Copy Query](#G - Copy Query)
注:本文并没有 ABC453F 的题解
A - Trimo
题意简述
输入一个字符串 \(S\) ,输出其去掉开头连续的所有字符 o 之后的结果。
解题思路
签到题。遍历字符串,找到第一个不是 o 的字符,然后从该字符开始输出剩余的字符串即可。
参考代码
cpp
// A - Trimo
// 2000 ms
// 1024 MB
// https://atcoder.jp/contests/abc453/tasks/abc453_a
// Made by Billlly 喵~
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int tests = 1;
void solve() {
int n;
std::cin >> n;
for (int i = 1; i <= n; ++i) {
char c;
std::cin >> c;
if (c == 'o')
continue;
else {
std::cout << c;
break;
}
}
std::string s;
std::cin >> s;
std::cout << s << '\n';
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
return 0;
}
B - Sensor Data Logging
题意简述
有一个传感器从 \(0 \sim T\ \text{s}\) 中的每一秒接到一个信号值 \(A_i\) 。
如果本次信号值与上次该传感器所记录的信号值绝对值之差大于等于 \(X\) ,则会记录此次的信号。
请你输出所有被记录的信号对应的时刻与信号值大小。
解题思路
签到题。直接模拟整个过程即可。初始记录 \(0\) 时刻的信号,之后遍历每个时刻,如果当前信号与上次记录的信号的绝对值差 \(\geq X\) ,则记录当前时刻的信号。
参考代码
cpp
// B - Sensor Data Logging
// 2000 ms
// 1024 MB
// https://atcoder.jp/contests/abc453/tasks/abc453_b
// Made by Billlly 喵~
// AC!
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
int T, X;
std::cin >> T >> X;
std::vector<int> A(T + 1);
for (int i = 0; i <= T; ++i)
std::cin >> A[i];
int lst = A[0];
std::cout << 0 << ' ' << A[0] << '\n';
for (int i = 1; i <= T; ++i) {
if (std::abs(A[i] - lst) >= X) {
std::cout << i << ' ' << A[i] << '\n';
lst = A[i];
}
}
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
return 0;
}
C - Sneaking Glances
题意简述
Takahashi 最开始位于数轴上 \(0.5\) 的位置。在接下来 \(N\) 次操作中,他每次必须向左或向右移动 \(L_i\) 个单位长度。问他最多能经过多少次原点(坐标为 \(0\) 的点)。
其中 \(1 \le N \le 20\) 。
解题思路
注意到 \(N\) 的范围很小,可以考虑状态压缩枚举。
枚举一个二进制掩码 mask ,表示每个 \(L_i\) 的移动方向(例如 \(1\) 表示向右,\(0\) 表示向左)。
对于每个枚举出的方向序列,模拟移动过程,并统计经过原点的次数。记录所有方案中的最大值即可。
时间复杂度为 \(O(n \cdot 2^n)\) 。
参考代码
cpp
// C - Sneaking Glances
// 2000 ms
// 1024 MB
// https://atcoder.jp/contests/abc453/tasks/abc453_c
// Made by Billlly 喵~
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int tests = 1;
void solve() {
int n;
std::cin >> n;
std::vector<real> l(n, 0ll);
for (int i = 0; i < n; ++i)
std::cin >> l[i];
int ans = 0;
for (int mask = 0; mask < (1 << n); ++mask) {
int cnt = 0;
real pos = 0.5;
for (int i = 0; i < n; ++i) {
bool flag1 = pos > 0;
if (mask & (1 << i))
pos += l[i];
else
pos -= l[i];
bool flag2 = pos < 0;
// 如果移动前后坐标符号相反,说明穿过了原点
cnt += not(flag1 xor flag2);
}
ans = std::max(ans, cnt);
}
std::cout << ans << '\n';
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
return 0;
}
D - Go Straight
题意简述
给定一张 \(N \times M\) 的网格地图,由以下字符构成:
#: 障碍,不可通行。.: 空地,可任意通行。o: 特殊点,进入后只能沿着进入的方向继续直线移动。x: 特殊点,进入后只能沿着非进入的方向(即其他三个方向)移动。S: 起点,可任意通行。G: 终点,可任意通行。
判断能否从起点到达终点。如果可以,需要给出一个长度小于 \(5 \times 10^6\) 的移动序列(由 U, D, L, R 组成)。
解题思路
这是一个规则稍复杂的 BFS 最短路问题。
关键点在于状态设计:除了位置 \((r, c)\) 外,还需要记录到达此位置时的"朝向" \(d\)(\(0,1,2,3\) 分别代表上、下、左、右,\(4\) 代表起点状态,无特定朝向)。
在 BFS 过程中,根据当前格子类型和朝向 \(d\) ,决定下一步可以走的方向集合 \(als\) :
- 如果当前格子是
S,.或G,或者 \(d = 4\)(起点),则可以向任意四个方向移动。 - 如果当前格子是
o,则只能沿原方向 \(d\) 移动。 - 如果当前格子是
x,则可以向除了 \(d\) 以外的其他三个方向移动。
在 BFS 过程中记录前驱状态,用于最后回溯构造路径。
由于状态数为 \(O(NM)\) ,每条边(状态转移)最多产生 \(4\) 个新状态,BFS 复杂度为 \(O(NM)\) ,可以接受。
参考代码
cpp
// D - Go Straight
// 2000 ms
// 1024 MB
// https://atcoder.jp/contests/abc453/tasks/abc453_d
// Made by Billlly 喵~
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
int h, w;
std::cin >> h >> w;
std::vector<std::string> g(h);
for (int i = 0; i < h; ++i)
std::cin >> g[i];
int sr, sc, er, ec;
for (int i = 0; i < h; ++i) {
for (int j = 0; j < w; ++j) {
if (g[i][j] == 'S') {
sr = i;
sc = j;
}
if (g[i][j] == 'G') {
er = i;
ec = j;
}
}
}
const int dr[4] = {-1, 1, 0, 0};
const int dc[4] = {0, 0, -1, 1};
const char ds[4] = {'U', 'D', 'L', 'R'};
auto stid = int r, int c, int d { return (r * w + c) * 5 + d; };
int ts = h * w * 5;
std::vector<char> vis(ts, 0);
std::vector<int> pre(ts, -1);
std::vector<char> mv(ts, 0);
std::queue<int> q;
int sid = stid(sr, sc, 4);
vis[sid] = 1;
q.emplace(sid);
int eid = -1;
while (not q.empty()) {
int id = q.front();
q.pop();
int d = id % 5;
int rc = id / 5;
int r = rc / w, c = rc % w;
if (r == er and c == ec) {
eid = id;
break;
}
std::vector<int> als;
char cc = g[r][c];
if (d == 4) {
als = {0, 1, 2, 3};
} else {
if (cc == 'o') {
als = {d};
} else if (cc == 'x') {
for (int nd = 0; nd < 4; nd += 1) {
if (nd != d) {
als.push_back(nd);
}
}
} else {
als = {0, 1, 2, 3};
}
}
for (int nd : als) {
int nr = r + dr[nd];
int nc = c + dc[nd];
if (nr < 0 or nr >= h or nc < 0 or nc >= w) {
continue;
}
if (g[nr][nc] == '#') {
continue;
}
int nid = stid(nr, nc, nd);
if (not vis[nid]) {
vis[nid] = 1;
pre[nid] = id;
mv[nid] = ds[nd];
q.emplace(nid);
}
}
}
if (eid == -1) {
std::cout << "No" << '\n';
return;
}
std::string pth;
int cur = eid;
while (cur != sid) {
pth.push_back(mv[cur]);
cur = pre[cur];
}
std::reverse(pth.begin(), pth.end());
std::cout << "Yes" << '\n';
std::cout << pth << '\n';
}
int main() {
std::ios::sync_with_stdio(0);
std::cout.tie(0);
std::cin.tie(0);
while (tests--) {
solve();
}
return 0;
}
E - Team Division
题意简述
有 \(N\) 个人,要分成两堆 \(A\) 和 \(B\) 。规则如下:
- 每队至少一人。
- 每个人必须属于恰好一队。
- 第 \(i\) 个人所在队伍的人数必须在 \([L_i, R_i]\) 区间内。
求合法的分组方案数,对 \(998244353\) 取模。
解题思路
考虑枚举 \(A\) 队的人数 \(a\) ,则 \(B\) 队人数为 \(N-a\) 。
对于选手 \(i\) :
- 如果他分到 \(A\) 队,需要满足 \(a \in [L_i, R_i]\) 。
- 如果他分到 \(B\) 队,需要满足 \(N-a \in [L_i, R_i]\) ,即 \(a \in [N-R_i, N-L_i]\) 。
对于一个给定的 \(a\) ,每个选手的合法去向可能是:
- 只能去 \(A\) 队(仅满足 \(A\) 条件)。
- 只能去 \(B\) 队(仅满足 \(B\) 条件)。
- 两者均可(同时满足两个条件)。
- 均不可(则该 \(a\) 不合法)。
我们遍历所有合法的 \(a\) (\(1 \le a \le N-1\)),利用差分数组统计:
- \(da[a]\) :满足 \(a \in [L_i, R_i]\) 的选手人数。
- \(db[a]\) :满足 \(N-a \in [L_i, R_i]\) 的选手人数(即 \(a \in [N-R_i, N-L_i]\))。
- \(dc[a]\) :同时满足两者的选手人数(即区间 \([L_i, R_i]\) 与 \([N-R_i, N-L_i]\) 的交集)。
前缀和后得到:
- \(onlyA = ca[a] - cc[a]\) (只能去 \(A\) 的人数)
- \(onlyB = cb[a] - cc[a]\) (只能去 \(B\) 的人数)
- \(both = cc[a]\) (两队皆可的人数)
对于一个 \(a\) 合法,必须满足:
- 所有选手都有去处:\(ca[a] + cb[a] - cc[a] = N\) 。
- \(onlyA \le a \le onlyA + both\) (\(A\) 队人数在限制内)。
- \(onlyB \le N-a \le onlyB + both\) (\(B\) 队人数在限制内)。
对于一个合法的 \(a\) ,方案数为从 \(both\) 人中选出 \(a - onlyA\) 人去 \(A\) 队,即 \(\binom{both}{a - onlyA}\) 。
累加所有合法 \(a\) 的方案数即为答案。
时间复杂度为 \(O(N)\) 。
参考代码
cpp
// E - Team Division
// 2000 ms
// 1024 MB
// https://atcoder.jp/contests/abc453/tasks/abc453_e
// Made by Billlly 喵~
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
constexpr static int mod = 998244353;
int n;
std::cin >> n;
std::vector<int> l(n), r(n);
for (int i = 0; i < n; ++i)
std::cin >> l[i] >> r[i];
std::vector<i64> da(n + 5, 0), db(n + 5, 0), dc(n + 5, 0);
for (int i = 0; i < n; ++i) {
int al = l[i];
int ar = r[i];
if (al <= ar) {
++da[al];
--da[ar + 1];
}
int bl = std::max(1, n - r[i]);
int br = std::min(n - 1, n - l[i]);
if (bl <= br) {
++db[bl];
--db[br + 1];
}
int cl = std::max({1, l[i], n - r[i]});
int cr = std::min({n - 1, r[i], n - l[i]});
if (cl <= cr) {
++dc[cl];
--dc[cr + 1];
}
}
std::vector<int> ca(n + 2, 0), cb(n + 2, 0), cc(n + 2, 0);
for (int a = 1; a <= n; ++a) {
da[a] += da[a - 1];
db[a] += db[a - 1];
dc[a] += dc[a - 1];
ca[a] = da[a];
cb[a] = db[a];
cc[a] = dc[a];
}
auto qpow = i64 base, i64 power {
i64 ret = 1;
while (power > 0) {
if (power & 1)
ret = ret * base % mod;
base = base * base % mod;
power >>= 1;
}
return ret;
};
std::vector<i64> fact(n + 5, 1), inv(n + 5, 1);
for (int i = 1; i <= n + 2; ++i)
fact[i] = fact[i - 1] * i % mod;
inv[n + 2] = qpow(fact[n + 2], mod - 2);
for (int i = n + 1; i >= 0; i -= 1)
inv[i] = inv[i + 1] * (i + 1) % mod;
auto C = int N, int K {
if (K < 0 or K > N)
return 0LL;
return fact[N] * inv[K] % mod * inv[N - K] % mod;
};
i64 ans = 0;
for (int a = 1; a < n; ++a) {
if (ca[a] + cb[a] - cc[a] != n)
continue;
int oa = ca[a] - cc[a];
int ob = cb[a] - cc[a];
int bc = cc[a];
if (oa <= a and a <= oa + bc and ob <= n - a and n - a <= ob + bc)
ans = (ans + C(bc, a - oa)) % mod;
}
std::cout << ans << '\n';
}
int main() {
std::ios::sync_with_stdio(0);
std::cout.tie(0);
std::cin.tie(0);
while (tests--) {
solve();
}
return 0;
}
G - Copy Query
题意简述
你有 \(N\) 个长度为 \(M\) 的序列 \(A_1 \sim A_N\) 。
现在你要进行 \(Q\) 次操作,每次操作类型如下:
1 X Y: 用序列 \(A_Y\) 替换 \(A_X\) 。2 X Y Z: 将 \(A_{X, Y}\) 替换为 \(Z\) 。3 X L R: 查询 \(A_X\) 中区间 \([L, R]\) 的和。
其中 \(N, M, Q \le 2 \times 10^5\) 。
解题思路
这道题是主席树(可持久化线段树)的经典应用场景。
- 操作 1 是将 \(X\) 序列的根节点
root[X]直接指向 \(Y\) 序列的根节点root[Y]。这是一个 \(O(1)\) 的操作。这利用了可持久化数据结构的特性,即多个"版本"可以共享大部分节点。 - 操作 2 是单点修改。在主席树上,这需要为 \(A_X\) 创建一个新版本,这个版本会新开 \(O(\log M)\) 个节点,并共享其他未修改的部分。时间复杂度 \(O(\log M)\)。
- 操作 3 是区间求和。直接在 \(A_X\) 对应的根节点版本上进行线段树查询即可。时间复杂度 \(O(\log M)\)。
初始时,所有序列对应同一个空树的根节点。每次修改操作(操作2)都会创建一个新版本。由于可持久化线段树的性质,总节点数约为 \(O(N + Q \log M)\) ,在题目限制下可以接受。
总时间复杂度为 \(O(Q \log M)\) 。
参考代码
cpp
// G - Copy Query
// 2000 ms
// 1024 MB
// https://atcoder.jp/contests/abc453/tasks/abc453_g
// Made by Billlly 喵~
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int tests = 1;
void solve() {
struct PresidentTree {
struct Node {
int lc, rc;
i64 sum;
Node(int _lc = 0, int _rc = 0, i64 _sum = 0ll)
: lc(_lc), rc(_rc), sum(_sum) {}
};
int n;
std::vector<Node> tree;
const int mxn = 8000000;
PresidentTree(int _n = 0) : n(_n) { tree.resize(mxn); }
int node_cnt = 0;
int new_node(int lc = 0, int rc = 0, i64 sum = 0) {
++node_cnt;
tree[node_cnt] = {lc, rc, sum};
return node_cnt;
}
int update(int cur, int l, int r, int pos, i64 val) {
if (l == r) {
return new_node(0, 0, val);
}
int mid = (l + r) >> 1;
int new_cur = new_node(tree[cur].lc, tree[cur].rc, 0);
if (pos <= mid) {
int left = update(tree[cur].lc, l, mid, pos, val);
tree[new_cur].lc = left;
} else {
int right = update(tree[cur].rc, mid + 1, r, pos, val);
tree[new_cur].rc = right;
}
tree[new_cur].sum =
tree[tree[new_cur].lc].sum + tree[tree[new_cur].rc].sum;
return new_cur;
}
i64 query(int cur, int l, int r, int ql, int qr) {
if (not cur or ql > r or qr < l)
return 0;
if (ql <= l and r <= qr)
return tree[cur].sum;
int mid = (l + r) >> 1;
return query(tree[cur].lc, l, mid, ql, qr) +
query(tree[cur].rc, mid + 1, r, ql, qr);
}
};
int n, m, q;
std::cin >> n >> m >> q;
std::vector<int> roots(n + 1, 0);
PresidentTree tree(n);
while (q--) {
int type;
std::cin >> type;
if (type == 1) {
int X, Y;
std::cin >> X >> Y;
roots[X] = roots[Y];
} else if (type == 2) {
int X, Y;
i64 Z;
std::cin >> X >> Y >> Z;
roots[X] = tree.update(roots[X], 1, m, Y, Z);
} else {
int X, L, R;
std::cin >> X >> L >> R;
std::cout << tree.query(roots[X], 1, m, L, R) << '\n';
}
}
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
return 0;
}