AtCoder Beginner Contest 320
A - Leyland Number (abc320 A)
题目大意
给定,输出 。
解题思路
因为数不超过,可以直接用 pow
计算然后转成。不会有精度损失。
神奇的代码
#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; cin >> a >> b; cout << int(pow(a, b) + pow(b, a)) << '\n'; return 0; }
B - Longest Palindrome (abc320 B)
题目大意
给定一个字符串,求长度最长的回文串。
解题思路
因为长度只有,可以直接枚举子串,然后暴力判断是不是回文串(翻转后是否和自身相同),复杂度为 。
数据范围大的话可以用Manachar
算法。
神奇的代码
#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); string s; cin >> s; int ans = 0; auto ok = [&](int l, int r) { string a = s.substr(l, r - l + 1); string b = a; reverse(b.begin(), b.end()); return a == b; }; for (int i = 0; i < s.size(); ++i) for (int j = i; j < s.size(); ++j) { if (ok(i, j)) ans = max(ans, j - i + 1); } cout << ans << '\n'; return 0; }
C - Slot Strategy 2 (Easy) (abc320 C)
题目大意
给定排灯,以及三个长度都为的数字串,表示每一时刻,每盏灯依次循环地显示这些数字。
对于某一时刻,你最多只能按一个按钮,从而让一盏灯停下来。当然你可以什么都不操作。
问最终让三盏灯停下来时显示的数字都一样,需要的最短时间。
解题思路
考虑最朴素的做法,首先枚举最终情况下,显示的数字是什么,有种情况。
然后考虑这三排灯按按钮的顺序,同样枚举这个顺序,有 种情况。
确定了数字和按按钮的顺序,剩下的就是模拟,只看一盏灯,然后出现这个数字时就按按钮,时间复杂度是 。
最终的时间复杂度是
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int inf = 1e9; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int m; cin >> m; array<string, 3> s; for (auto& i : s) cin >> i; int ans = 1e9; for (int i = 0; i <= 9; ++i) { array<int, 3> id{}; iota(id.begin(), id.end(), 0); do { int cur = 0; int tot = 0; for (auto& j : id) { if (s[j].find(i + '0') == string::npos) { tot = inf; break; } while (s[j][cur] - '0' != i) { cur = (cur + 1) % m; ++tot; } cur = (cur + 1) % m; ++tot; } ans = min(ans, tot); } while (next_permutation(id.begin(), id.end())); } if (ans == inf) ans = 0; cout << ans - 1 << '\n'; return 0; }
D - Relative Position (abc320 D)
题目大意
二维平面,有个人,其中第一个人在原点。
给定 条信息 ,表示第 个人的位置是 。其中表示第 个人的位置。
问每个人的位置,或告知不确定。
解题思路
初始只有第一个人的位置确定。那么所有与第一个人相关的信息的其他人的位置都能确定。
有更多的人的位置确定了,那么与这些人相关的信息的更多的人的位置都可以确定。
这感觉上就像是一个关系网,从第一个人往外传播,遍及到能遍及到的人。
这就是一张图,我们根据这些信息建立一张无向图,然后从号点开始 ,确定中间每个人的位置。
未遍历到的点就是位置不确定的人。
神奇的代码
#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<vector<array<int, 3>>> edge(n); for (int i = 0; i < m; ++i) { int a, b, x, y; cin >> a >> b >> x >> y; --a, --b; edge[a].push_back({b, x, y}); edge[b].push_back({a, -x, -y}); } queue<int> team; vector<int> ok(n); vector<array<LL, 2>> pos(n, {0, 0}); ok[0] = 1; team.push(0); while (!team.empty()) { auto u = team.front(); team.pop(); for (auto& [v, x, y] : edge[u]) { if (!ok[v]) { pos[v] = {pos[u][0] + x, pos[u][1] + y}; ok[v] = 1; team.push(v); } } } for (int i = 0; i < n; ++i) { if (ok[i]) cout << pos[i][0] << ' ' << pos[i][1] << '\n'; else cout << "undecidable" << '\n'; } return 0; }
E - Somen Nagashi (abc320 E)
题目大意
给定个人,从左到右。
有 个事件,每个事件形如 ,表示第 时刻,当前最左边的人可以获得 的分数,然后离开,直到 时刻回来。
问最后每个人的分数。
解题思路
朴素的思想就是每一时刻迭代,但时刻高达。注意到有很多时刻都是无事发生的,因此我们只用考虑有事件发生的,即有人离开和有人回来。
问题需要维护两个信息,一个是最左边的人的标号(就是当前最小的标号),以及何时会有人回来这一事件的发生。
注意到每次都是剔除标号最小的人,以及获取的标号最小的人,因此可以用一个优先队列维护当前还在的人的标号,是个小根堆。
至于维护有人何时会回来这一事件,注意到这个事件是有一个发生时间,而我们是按照时间流逝依次处理的,因此我们可以用另一个优先队列维护这些时间,这样就可以动态插入新的事件,通过时间从小到大排序,以及当前时刻就能判断事件是否发生。
两个优先队列模拟这个过程即可。
神奇的代码
#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; priority_queue<int> team; for (int i = 0; i < n; ++i) team.push(-i); priority_queue<array<int, 2>> event; vector<LL> ans(n); for (int i = 0; i < m; ++i) { int t, w, s; cin >> t >> w >> s; while (!event.empty() && -event.top()[0] <= t) { auto [_, u] = event.top(); event.pop(); team.push(-u); } if (!team.empty()) { int u = -team.top(); team.pop(); ans[u] += w; event.push({-(t + s), u}); } } for (auto& i : ans) cout << i << '\n'; return 0; }
F - Fuel Round Trip (abc320 F)
题目大意
一维数轴,从出发到 ,再折返回来。
开车,油箱容量 ,一距离消耗一单位汽油,有 个加油站 ,对于第个加油站,其位于 ,可花元加油 单位汽油,但不能超过,超过部分则不要了,且花费价格不变 。
问出发再折返回来,消耗的最小钱数。注意一个加油站只能使用一次,这意味着如果过去用了,折返回来时不能再用了。
解题思路
本题有几个特别的地方,一个是油箱有上限,一个是加油是全加,另一个是有折返,加油站只能用一次。
如果没有加油站只能用一次的情况,注意到和 只有 。
考虑爆搜时的状态信息,我们可以保留 表示到达第 个加油站,此时还剩下 升汽油的最小花费。然后枚举在第 个加油站时的汽油状态,考虑当前加油站是否加油,转移即可。
但是这个状态在返程时会有问题,因为对于第个加油站能否加油,取决于去时是否加油,但这在状态里,这一信息没有保留下来。而记录每个加油站的使用状态,这是个指数级的状态,不行。
考虑棘手的地方,就是返程时到达了第个加油站,此时无法做决策,因为我不知道来时,在第 个加油站的状态。而如果我能一次就考虑第个加油站,来回这两次的加油状态,就不会有上述问题。
思考如何一次就能考虑来回两次的状态 :即来时到达第个加油站,此时有一个油箱量,回时到达第 个加油站 ,也有一个油箱量。
设表示从个加油站出发,此时油箱量为(该站加油前),回来时油箱量为(该站加油后)的最小费用。那么当前加油站一来一回的最优策略, 可以从下一个加油站一来一回的最优策略转移,枚举第个加油站时的状态,以及考虑当前加油站来回时是否加油即可。
最后答案就是 的最小值。初始条件为 ,其余为无穷大。
上述是 的整体方向,下述是一些实现细节,主要是由于油箱的上限和时间复杂度的优化。
代码里第一维是压缩掉了,实际维护的 表示,在第 个加油站,出发时 油箱量为,回来时为 ,且回来时没加油/加了油的最小费用。注意这里的油箱量都是加油前的量,比如我选择出发时加了油,那么实际我转移时,容量起始为 ,如果我选择回来时加了油,那么实际转移时,容量起始为 。
循环枚举的 是从第 个加油站出发时的油量(加油前), 是从第 个加油站回来时的油量(加油后)。
这样在转移时直接通过路程来计算 去到时 油量和回到时的油量,不然如果枚举的都是第 个加油站的状态的话,如果回来时加油了,且 为 ,要从转移过来,这要考虑它的加油前的状态 ,分别有,这又会有 的状态要枚举。
新增的 状态也是为了能够转移而加上的。
于是代码看着就比较复杂了(但是大方向是上述的三维,应该会有更优美的实现方式。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int inf = 1e9; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, h; cin >> n >> h; vector<int> pos(n + 1, 0); for (int i = 1; i <= n; ++i) cin >> pos[i]; vector<array<int, 2>> info(n + 1, {0, 0}); for (int i = 1; i < n; ++i) { cin >> info[i][0] >> info[i][1]; } vector<vector<array<int, 2>>> dp(h + 1, vector<array<int, 2>>(h + 1, {inf, inf})); for (int i = 0; i <= h; ++i) dp[i][i][0] = 0; for (int i = n - 1; i >= 0; --i) { vector<vector<array<int, 2>>> dp2( h + 1, vector<array<int, 2>>(h + 1, {inf, inf})); int dis = pos[i + 1] - pos[i]; auto [p, f] = info[i]; auto [np, nf] = info[i + 1]; for (int j = 0; j <= h; ++j) { for (int k = 0; k <= h; ++k) { if (j - dis >= 0 && k - dis >= 0) dp2[j][k - dis][0] = min(dp2[j][k - dis][0], dp[j - dis][k][0]); if (j - dis >= 0 && min(k + nf, h) - dis >= 0) dp2[j][min(k + nf, h) - dis][0] = min(dp2[j][min(k + nf, h) - dis][0], dp[j - dis][k][1]); if (min(j + f, h) - dis >= 0 && k - dis >= 0) dp2[j][k - dis][0] = min(dp2[j][k - dis][0], dp[min(j + f, h) - dis][k][0] + p); if (min(j + f, h) - dis >= 0 && min(k + nf, h) - dis >= 0) dp2[j][min(k + nf, h) - dis][0] = min(dp2[j][min(k + nf, h) - dis][0], dp[min(j + f, h) - dis][k][1] + p); if (j - dis >= 0 && k - dis >= 0) dp2[j][k - dis][1] = min(dp2[j][k - dis][1], dp[j - dis][k][0] + p); if (j - dis >= 0 && min(k + nf, h) - dis >= 0) dp2[j][min(k + nf, h) - dis][1] = min( dp2[j][min(k + nf, h) - dis][1], dp[j - dis][k][1] + p); } } dp.swap(dp2); } int ans = inf; for (int i = 0; i <= h; ++i) ans = min(ans, dp[h][i][0]); if (ans == inf) ans = -1; cout << ans << '\n'; return 0; }
一觉醒来想到了更优美的写法,同样是设表示从个加油站出发,此时油箱量为(该站加油前),回来时油箱量为(该站加油后)的最小费用。注意到去时的油箱要考虑加油前的,回是要考虑加油后的。至于原因,可以分别考虑加油前后的共四种情况,会发现某些情况可能会转移式不一样,某些可能转移的复杂度会更高。
循环枚举的 是从第 个加油站出发时的油量(加油前),是回到第 个加油站的油量(加油前)。
这样转移时对于第个加油站的情况就可以唯一确定了。转移式就更简洁一点,
注意枚举的并不是 式的 的意义,如果是加油后的话,再计算第 个加油站的情况时,可能是 ,也可能是 ,因为加油是 。这会新增一个 的复杂度。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int inf = 1e9; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, h; cin >> n >> h; vector<int> pos(n + 1, 0); for (int i = 1; i <= n; ++i) cin >> pos[i]; vector<array<int, 2>> info(n + 1, {0, 0}); for (int i = 1; i < n; ++i) { cin >> info[i][0] >> info[i][1]; } vector<vector<int>> dp(h + 1, vector<int>(h + 1, inf)); for (int i = 0; i <= h; ++i) dp[i][i] = 0; for (int i = n - 1; i >= 0; --i) { vector<vector<int>> dp2(h + 1, vector<int>(h + 1, inf)); int dis = pos[i + 1] - pos[i]; auto [p, f] = info[i]; for (int j = 0; j <= h; ++j) { for (int k = 0; k <= h; ++k) { // 不加油 if (j - dis >= 0 && k + dis <= h) dp2[j][k] = min(dp2[j][k], dp[j - dis][k + dis]); // 去时加油 if (min(j + f, h) - dis >= 0 && k + dis <= h) dp2[j][k] = min(dp2[j][k], dp[min(j + f, h) - dis][k + dis] + p); // 回时加油 if (j - dis >= 0 && k + dis <= h) dp2[j][min(k + f, h)] = min(dp2[j][min(k + f, h)], dp[j - dis][k + dis] + p); } } dp.swap(dp2); } int ans = *min_element(dp[h].begin(), dp[h].end()); if (ans == inf) ans = -1; cout << ans << '\n'; return 0; }
G - Slot Strategy 2 (Hard) (abc320 G)
题目大意
给定排灯,以及长度都为的数字串,表示每一时刻,每盏灯依次循环地显示这些数字。
对于某一时刻,你最多只能按一个按钮,从而让一盏灯停下来。当然你可以什么都不操作。
问最终让盏灯停下来时显示的数字都一样,需要的最短时间。
解题思路
<++>
神奇的代码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现