AtCoder Beginner Contest 326
A - 2UP3DOWN (abc326 A)
题目大意
100楼层,一次可以上最多两层,或下最多三层。
给定两楼层,问能否一次到达。
解题思路
比较大小,然后判断其差是是不是在或 内即可。
神奇的代码
#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 x, y; cin >> x >> y; if (x > y && x - y <= 3) cout << "Yes" << '\n'; else if (x <= y && y - x <= 2) cout << "Yes" << '\n'; else cout << "No" << '\n'; return 0; }
B - 326-like Numbers (abc326 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 n; cin >> n; auto is_326 = [](int x) { auto s = to_string(x); return (s[0] - '0') * (s[1] - '0') == (s[2] - '0'); }; while (!is_326(n)) ++n; cout << n << '\n'; return 0; }
C - Peak (abc326 C)
题目大意
给定一维数轴上的个礼物点,问长度为的左闭右开的区间最多有多少个点。
解题思路
注意到最好情况下,其区间的左端点一定是礼物点。
因此我们可以枚举左端点所在的礼物点,然后看看到右端点时包含了多少个礼物。这个可以先对礼物点排序然后二分找到右端点礼物,作差得到数量。
当然也可以双指针。
ranges::
的用法是c++20
的特性。
神奇的代码
#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, m; cin >> n >> m; vector<int> a(n); for (auto& i : a) cin >> i; ranges::sort(a); int ans = 0; for (int i = 0; i < n; ++i) { ans = max(ans, int(ranges::lower_bound(a, a[i] + m) - a.begin() - i)); } cout << ans << '\n'; return 0; }
D - ABC Puzzle (abc326 D)
题目大意
给定两个仅包含ABC
的长度为的串,要求构造一个的矩阵,要求:
- 每行每列包含恰好一个
ABC
- 每行最左边的字母组成字符串
- 每列最顶部的字母组成字符串
解题思路
因为只有 ,考虑朴素搜索。
每行ABC
的排列情况只有种, 行的总情况为 ,但如果中间剪枝的话其合法情况数远远小于该数。
具体来说,考虑当前位置 放什么,我们需要第 行的放置情况 ,第 列的放置情况 ,以决定该位置能否放该字母(代码里是二进制压缩的状态)。还需要 表示第 行的是否放过字母,以决定是否和字符串 比较,同理也需要 表示第 列是否放过字母,以决定是否和字符串 比较。这里的剪枝可以减去大部份合法情况。
在考虑完一行后,可以看看 是否放了ABC
三个字母,没放满则直接剪枝。
感觉写的很复杂。
神奇的代码
#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 a, b; cin >> n >> a >> b; vector<vector<int>> ans(n, vector<int>(n, -1)); vector<int> top(n, 1); vector<int> left(n, 1); vector<int> row(n); vector<int> col(n); auto not_have = [&](int x, int y) { return ((~x >> y) & 1); }; function<bool(int, int)> dfs = [&](int x, int y) { if (x == n) { for (auto& i : row) if (i != 7) return false; for (auto& i : col) if (i != 7) return false; return true; } for (int i = 0; i < 3; ++i) { if (not_have(row[x], i) && not_have(col[y], i)) { if (top[y] && b[y] - 'A' != i) continue; if (left[x] && a[x] - 'A' != i) continue; row[x] |= (1 << i); col[y] |= (1 << i); ans[x][y] = i; int ttop = top[y], lleft = left[x]; top[y] = 0; left[x] = 0; if (y == n - 1) { if (row[x] == 7 && dfs(x + 1, 0)) return true; } else { if (dfs(x, y + 1)) return true; } row[x] ^= (1 << i); col[y] ^= (1 << i); ans[x][y] = -1; top[y] = ttop; left[x] = lleft; } } if (y == n - 1) { if (row[x] != 7) return false; if (dfs(x + 1, 0)) return true; } else { if (dfs(x, y + 1)) return true; } return false; }; if (dfs(0, 0)) { cout << "Yes" << '\n'; for (auto& i : ans) { for (auto& j : i) { if (j == -1) cout << '.'; else cout << char(j + 'A'); } cout << '\n'; } } else cout << "No" << '\n'; return 0; }
E - Revenge of "The Salary of AtCoder Inc." (abc326 E)
题目大意
给定包含个数字的数组 ,以及一个等概率的 面骰子。进行以下游戏:
初始,投掷一次骰子,得到
- 如果,则获得 元,同时令
- 否则游戏结束
问期望获得的钱数。
解题思路
期望题,根据定义,一个状态的期望,它是所有后继状态的期望的加权平均,这里的权重是概率。
当前状态是,则设表示当前掷出的值为 ,继续游戏至游戏结束,这期间获得的期望钱数。
很显然初始条件 ,即 时,无论怎么骰,始终有 ,游戏总是会结束。
考虑一般情况,当前状态是,考虑投掷一次骰子的后继状态:
- 有的概率投掷出,此时游戏结束,后继状态的期望收入为 。
- 有的概率投掷出,此时进入后继状态,获得收益
- 有的概率投掷出,此时进入后继状态,获得收益
- 有的概率投掷出,此时进入后继状态,获得收益
- ...
- 有的概率投掷出,此时进入后继状态,获得收益
由此可以写出转移式子:
这是一个后缀和,求的时候维护一下就可以了。观察到数组和后缀和的关系,代码里直接把数组省掉了。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int mo = 998244353; long long qpower(long long a, long long b) { long long qwq = 1; while (b) { if (b & 1) qwq = qwq * a % mo; a = a * a % mo; b >>= 1; } return qwq; } long long inv(long long x) { return qpower(x, mo - 2); } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; vector<int> a(n); for (auto& i : a) cin >> i; int presum = 0, invn = inv(n); for (int i = n - 1; i >= 0; --i) { presum = (0ll + presum + 1ll * presum * invn % mo + a[i]) % mo; } cout << 1ll * presum * invn % mo << '\n'; return 0; }
F - Robot Rotation (abc326 F)
题目大意
二维平面,机器人初始 ,面向轴正方向。给定次移动的距离数组,每次移动前,你需要指定机器人顺时针或逆时针转 度,随后移动。
问能否移动到达目标点 ,能移动到则给出一个可行的旋转序列。
解题思路
注意到只有,且每次必须旋转机器人,这意味着机器人有一半是上下移动,一半是左右移动,最多 次。
横纵坐标的移动我们是可以分开考虑的,因此我们就分别考虑横坐标移动的 次操作和纵坐标移动的 次操作。
而操作,事实上就是决定移动距离的正负性。注意到 其实是个很微妙的性质,它的一半 是可以承受指数的复杂度的。即采用折半搜索的策略。
即对于这 次操作,我们要对它们赋予正负号,看它们的和能否成为 目标移动距离(或 )。
我们可以分别对前个数和后,花 枚举它们的正负号,记录它们的和。
然后再把这两部份的结果合并:能否从左右两边的结果各挑一个数,它们的和。这是一个非常简单的子问题,对两边排序后一个双指针就可以解决了。这里的时间复杂度是 。
分别对方向进行一次折半搜索,就可以搜到结果了。
由于要输出方案,因此我们得记录该结果的每一项的正负号,下面代码是通过二进制压缩的形式记录的,最后还要通过每一项的正负号,以及当前机器人的朝向决定向左向右转。
神奇的代码
#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, x, y; cin >> n >> x >> y; array<vector<int>, 2> a; for (int i = 0; i < n; ++i) { int x; cin >> x; a[i & 1].push_back(x); } auto dfs = [&](auto self, const vector<int>& a, int pos, int sum, int sol, int l, int r, vector<array<int, 2>>& tmp) { if (pos + l == r) { tmp.push_back({sum, sol}); return; } self(self, a, pos + 1, sum + a[pos + l], sol, l, r, tmp); self(self, a, pos + 1, sum - a[pos + l], (sol | (1 << pos)), l, r, tmp); }; auto solve = [&](const vector<int>& a, int val) { int mid = a.size() / 2; vector<array<int, 2>> l, r; dfs(dfs, a, 0, 0, 0, 0, mid, l); dfs(dfs, a, 0, 0, 0, mid, a.size(), r); vector<int> lid(l.size()), rid(r.size()); iota(lid.begin(), lid.end(), 0); iota(rid.begin(), rid.end(), 0); ranges::sort(lid, [&](int x, int y) { return l[x][0] < l[y][0]; }); ranges::sort(rid, [&](int x, int y) { return r[x][0] > r[y][0]; }); auto lp = lid.begin(), rp = rid.begin(); while (lp != lid.end() && rp != rid.end()) { if (l[*lp][0] + r[*rp][0] == val) { return l[*lp][1] + ((1ll * r[*rp][1]) << mid); } else if (l[*lp][0] + r[*rp][0] < val) { lp = next(lp); } else if (l[*lp][0] + r[*rp][0] > val) { rp = next(rp); } } return -1ll; }; auto dy = solve(a[0], y); // 0 => +, 1 => - auto dx = solve(a[1], x); if (dy == -1 || dx == -1) cout << "No" << '\n'; else { cout << "Yes" << '\n'; vector<int> ans; for (int i = 0; i <= n / 2; ++i) { ans.push_back((dy >> i) & 1); if (i < n / 2) ans.push_back((dx >> i) & 1); } int dir = 0; for (int i = 0; i < n; ++i) { cout << "LR"[ans[i] ^ dir ^ (i & 1)]; dir = ans[i]; } cout << '\n'; } return 0; }
G - Unlock Achievement (abc326 G)
题目大意
给定个技能和 个成就。对于第 个技能,升一级花费 。
达成第 个成就, 有的奖励,达成条件为,对于每个技能要达到指定等级或以上。
问最大的收益,即奖励花费的最大值。
解题思路
考虑只有一个技能和一个成就,那我们选择是否完成这个成就,就看其技能升级的成本
和成就的奖励
的大小。
当考虑多个一个技能和多个成就时,成本
和奖励
之间的衡量就没那么单纯:我可能完成了一个成就,而当我升级一次技能,又能立刻完成另一个成就——即不用从零等级技能考虑。这种有泛泛的,复杂关系决策的,网络流可能会比较在行。
成就只有达成
与未达成
两种状态,而技能有5种状态,但如果根据完成成就的条件——某技能等级于x
或以上,将技能拆分成这种情况,那么这些情况同样也只有 达成
与未达成
两种状态。
接下来要做的就是规定每个成就和每个技能状态,是达成
或不达成
,其中会有一些代价,这种二分类问题的最小化代价,可以转换成最小割问题。而最小割的问题可以转换成最大流求解。
考虑一张图,有源点和汇点,其余点就是上述所说的状态,即每个成就表示成一个点,每个技能拆分成5个点,分别表示该技能等级达到i级或以上
。
一个割
会将这张图分成两部分,一部分与相连,一部分与相连。可以规定与相连的点表示状态 未达成
,与相连的点表示达成
。
由于最小割是最小化代价,而成就奖励要最大化,这里需要转换一下,先假设完成了所有成就,那我们的决策就是决定成就是否不完成
,即最小化未达成成就的代价
。
考虑如何连边以及边权(容量)设定,注意到网络流里,当一条边的容量满了,意味着这条边被割去了,即不与(或 )相连,换句话说边权就是不成为某一类的代价
。
对于成就
这一状态,它不成为达成
状态的代价就是a_i
,即如果我选择不达成该成就,即与的边断开,则我将失去其奖励 。因此每个成就与汇点连一条容量为 的边。
对于 技能
,它有5个状态,(以下等级1
表示技能等级达到1或以上
)除了等级1
状态外,源点与其他状态 连一条容量为的边,即升级一次的代价。比如 等级2
状态,我花费升级一次至等级 ,那该状态就是 达成
的,它不成为未达成
这一状态了。
当然这里需要避免一种情况,就是等级3
达成了,但等级2
未达成。我们需要从等级2
连一条边到等级3
,其容量为无穷大
。这样子,尽管的边割去了,还存在 的路径,要割去这条路径,只能把也割去,这样子 等级2
也达成了。因为是无穷大的,所以这条边不会被割去,它只能割,因为其代价更小。这意味着技能等级之间的依赖关系不能被割去。
还有一种避免的情况就是,成就要求的技能等级都达成了,但成就未达成
,这需要连一条从对应的技能状态
到该成就
的容量为 无穷大
的边,这意味着技能和成就之间的关系不能被割去。
建好图后,跑一遍最大流得最小割,用成就总奖励减去最小割即为答案。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; template <typename T> class flow_graph { public: static constexpr T eps = (T)1e-9; struct edge { int from; int to; T c; T f; }; vector<vector<int>> g; vector<edge> edges; int n; int st; int fin; T flow; flow_graph(int _n, int _st, int _fin) : n(_n), st(_st), fin(_fin) { assert(0 <= st && st < n && 0 <= fin && fin < n && st != fin); g.resize(n); flow = 0; } void clear_flow() { for (const edge& e : edges) { e.f = 0; } flow = 0; } int add(int from, int to, T forward_cap, T backward_cap) { assert(0 <= from && from < n && 0 <= to && to < n); int id = (int)edges.size(); g[from].push_back(id); edges.push_back({from, to, forward_cap, 0}); g[to].push_back(id + 1); edges.push_back({to, from, backward_cap, 0}); return id; } }; template <typename T> class dinic { public: flow_graph<T>& g; vector<int> ptr; vector<int> d; vector<int> q; dinic(flow_graph<T>& _g) : g(_g) { ptr.resize(g.n); d.resize(g.n); q.resize(g.n); } bool expath() { fill(d.begin(), d.end(), -1); q[0] = g.fin; d[g.fin] = 0; int beg = 0, end = 1; while (beg < end) { int i = q[beg++]; for (int id : g.g[i]) { const auto& e = g.edges[id]; const auto& back = g.edges[id ^ 1]; if (back.c - back.f > g.eps && d[e.to] == -1) { d[e.to] = d[i] + 1; if (e.to == g.st) { return true; } q[end++] = e.to; } } } return false; } T dfs(int v, T w) { if (v == g.fin) { return w; } int& j = ptr[v]; while (j >= 0) { int id = g.g[v][j]; const auto& e = g.edges[id]; if (e.c - e.f > g.eps && d[e.to] == d[v] - 1) { T t = dfs(e.to, min(e.c - e.f, w)); if (t > g.eps) { g.edges[id].f += t; g.edges[id ^ 1].f -= t; return t; } } j--; } return 0; } T max_flow() { while (expath()) { for (int i = 0; i < g.n; i++) { ptr[i] = (int)g.g[i].size() - 1; } T big_add = 0; while (true) { T add = dfs(g.st, numeric_limits<T>::max()); if (add <= g.eps) { break; } big_add += add; } if (big_add <= g.eps) { break; } g.flow += big_add; } return g.flow; } vector<int> min_cut() { max_flow(); vector<int> ret(g.n); for (int i = 0; i < g.n; i++) { ret[i] = (d[i] != -1); } return ret; } }; const int inf = 1e9 + 7; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; vector<int> c(n), a(m); for (auto& i : c) cin >> i; for (auto& i : a) cin >> i; int st = 0, pst = st + 1, ast = pst + n * 5, ed = ast + m, num = ed + 1; flow_graph<int> g(num, st, ed); for (int i = 0; i < n; ++i) { int l = i * 5, r = l + 5; for (int j = l; j < r; ++j) { int pj = pst + j; if (j == l) { g.add(pj, ed, inf, 0); } else { g.add(st, pj, c[i], 0); g.add(pj - 1, pj, inf, 0); } } } for (int i = 0; i < m; ++i) { int ai = ast + i; g.add(ai, ed, a[i], 0); } for (int i = 0; i < m; ++i) for (int j = 0; j < n; ++j) { int x; cin >> x; --x; int ai = ast + i, pj = pst + j * 5 + x; g.add(pj, ai, inf, 0); } dinic solver(g); int ans = accumulate(a.begin(), a.end(), 0) - solver.max_flow(); cout << ans << '\n'; return 0; }
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/17794865.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步