AtCoder Beginner Contest 320

A - Leyland Number (abc320 A)

题目大意

给定a,b,输出 ab+ba

解题思路

因为数不超过10,可以直接用 pow计算然后转成int。不会有精度损失。

神奇的代码
#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)

题目大意

给定一个字符串s,求长度最长的回文串。

解题思路

因为长度只有100,可以直接枚举子串,然后暴力判断是不是回文串(翻转后是否和自身相同),复杂度为 O(n3)

数据范围大的话可以用Manachar算法O(n)

神奇的代码
#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)

题目大意

给定n=3排灯,以及三个长度都为m的数字串,表示每一时刻,每盏灯依次循环地显示这些数字。

对于某一时刻,你最多只能按一个按钮,从而让一盏灯停下来。当然你可以什么都不操作。

问最终让三盏灯停下来时显示的数字都一样,需要的最短时间。

解题思路

考虑最朴素的做法,首先枚举最终情况下,显示的数字是什么,有10种情况。

然后考虑这三排灯按按钮的顺序,同样枚举这个顺序,有 3!种情况。

确定了数字和按按钮的顺序,剩下的就是模拟,只看一盏灯,然后出现这个数字时就按按钮,时间复杂度是 O(m)

最终的时间复杂度是O(n!×10nm)

神奇的代码
#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)

题目大意

二维平面,有n个人,其中第一个人在原点。

给定 m条信息 (a,b,x,y),表示第 b个人的位置是 ax+x,ay+y。其中ax,ay表示第 a个人的位置。

问每个人的位置,或告知不确定。

解题思路

初始只有第一个人的位置确定。那么所有与第一个人相关的信息的其他人的位置都能确定。

有更多的人的位置确定了,那么与这些人相关的信息的更多的人的位置都可以确定。

这感觉上就像是一个关系网,从第一个人往外传播,遍及到能遍及到的人。

这就是一张图,我们根据这些信息建立一张无向图,然后从1号点开始 BFS,确定中间每个人的位置。

未遍历到的点就是位置不确定的人。

神奇的代码
#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)

题目大意

给定n个人,从左到右。

q个事件,每个事件形如 (t,w,s),表示第 t时刻,当前最左边的人可以获得 w的分数,然后离开,直到 t+s时刻回来。

问最后每个人的分数。

解题思路

朴素的思想就是每一时刻迭代,但时刻高达109。注意到有很多时刻都是无事发生的,因此我们只用考虑有事件发生的,即有人离开和有人回来。

问题需要维护两个信息,一个是最左边的人的标号(就是当前最小的标号),以及何时会有人回来这一事件的发生。

注意到每次都是剔除标号最小的人,以及获取的标号最小的人,因此可以用一个优先队列维护当前还在的人的标号,是个小根堆。

至于维护有人何时会回来这一事件,注意到这个事件是有一个发生时间t+s,而我们是按照时间流逝依次处理的,因此我们可以用另一个优先队列维护这些时间,这样就可以动态插入新的事件,通过时间从小到大排序,以及当前时刻就能判断事件是否发生。

两个优先队列模拟这个过程即可。

神奇的代码
#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)

题目大意

一维数轴,从0出发到 xn,再折返回来。

开车,油箱容量 h,一距离消耗一单位汽油,有 n1个加油站 ,对于第i个加油站,其位于 xi,可花pi元加油 fi单位汽油,但不能超过h,超过部分则不要了,且花费价格不变 。

问出发再折返回来,消耗的最小钱数。注意一个加油站只能使用一次,这意味着如果过去用了,折返回来时不能再用了。

解题思路

本题有几个特别的地方,一个是油箱有上限,一个是加油是全加,另一个是有折返,加油站只能用一次。

如果没有加油站只能用一次的情况,注意到nh只有 300

考虑爆搜时的状态信息,我们可以保留 dp[i][j]表示到达第 i个加油站,此时还剩下 j升汽油的最小花费。然后枚举在第 i1 个加油站时的汽油状态,考虑当前加油站是否加油,转移即可。

但是这个状态在返程时会有问题,因为对于第i个加油站能否加油,取决于去时是否加油,但这在状态里,这一信息没有保留下来。而记录每个加油站的使用状态,这是个指数级的状态,不行。

考虑棘手的地方,就是返程时到达了第i个加油站,此时无法做决策,因为我不知道来时,在第 i个加油站的状态。而如果我能一次就考虑第i个加油站,来回这两次的加油状态,就不会有上述问题。

思考如何一次就能考虑来回两次的状态 :即来时到达第i个加油站,此时有一个油箱量,回时到达第 i个加油站 ,也有一个油箱量。

dp[i][j][k]表示从i个加油站出发,此时油箱量为j(该站加油前),回来时油箱量为k(该站加油后)的最小费用。那么当前加油站一来一回的最优策略, 可以从下一个加油站一来一回的最优策略转移,枚举第i+1个加油站时的状态,以及考虑当前加油站来回时是否加油即可。

最后答案就是 dp[0][h][]的最小值。初始条件为 dp[n][i][i]=0,其余为无穷大。

上述是 dp的整体方向,下述是一些实现细节,主要是由于油箱的上限和时间复杂度的优化。

代码里第一维是压缩掉了,实际维护的 dp[i][j][k][0/1]表示,在第 i个加油站,出发时 油箱量为j,回来时为 k,且回来时没加油/加了油的最小费用。注意这里的油箱量都是加油前的量,比如我选择出发时加了油,那么实际我转移时,容量起始为 min(j+f,h),如果我选择回来时加了油,那么实际转移时,容量起始为 min(k+f,h)

循环枚举的 j是从第 i个加油站出发时的油量(加油前), k是从第 i+1个加油站回来时的油量(加油后)。

这样在转移时直接通过路程来计算 去到i+1时 油量和回到i时的油量,不然如果枚举的都是第 i个加油站的状态的话,如果回来时加油了,且 kh,要从i+1转移过来,这要考虑它的加油前的状态 ,分别有h,h1,h2,...,hf,这又会有 O(h)的状态要枚举。

新增的 0/1状态也是为了能够转移而加上的。

于是代码看着就比较复杂了(但是大方向是上述的三维dp,应该会有更优美的实现方式。

神奇的代码
#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;
}


一觉醒来想到了更优美的写法,同样是设dp[i][j][k]表示从i个加油站出发,此时油箱量为j(该站加油前),回来时油箱量为k(该站加油后)的最小费用。注意到去时的油箱要考虑加油前的,回是要考虑加油后的。至于原因,可以分别考虑加油前后的共四种情况,会发现某些情况可能会转移式不一样,某些可能转移的复杂度会更高。

循环枚举的 j是从第 i个加油站出发时的油量(加油前),k回到i个加油站的油量(加油前)。

这样转移时对于第i+1个加油站的情况就可以唯一确定了。转移式就更简洁一点,

注意枚举的k并不是 dp式的 k的意义,如果是加油后的话,再计算第 i+1个加油站的情况时,可能是 kf+dis,也可能是 k1+dis,因为加油是 min(k+f,h)。这会新增一个 O(n)的复杂度。

神奇的代码
#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)

题目大意

给定n排灯,以及长度都为m的数字串,表示每一时刻,每盏灯依次循环地显示这些数字。

对于某一时刻,你最多只能按一个按钮,从而让一盏灯停下来。当然你可以什么都不操作。

问最终让n盏灯停下来时显示的数字都一样,需要的最短时间。

解题思路

<++>

神奇的代码


posted @   ~Lanly~  阅读(502)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示