AtCoder Beginner Contest 375
省流版
- A. 枚举所有子串判断即可
- B. 模拟计算记录累加即可
- C. 根据旋转的周期性对每个点旋转次数取模后暴力旋转即可
- D. 枚举,考虑 的对数,写成数学表达式后维护个数和位置和即可
- E. 背包问题,以前两个数组和为状态,考虑每个数移动到何处转移即可
- F. 逆向,删边变加边,维护加边后的距离变化即可
- G. 最短路径图的割边判断,根据最短路条数判断是否必经即可
A - Seats (abc375 A)
题目大意
给定一个字符串,问有多少个形如#.#
的子串。
解题思路
枚举所有子串依次比较即可。
神奇的代码
#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 s; cin >> n >> s; int ans = 0; for (int i = 0; i < n; ++i) { ans += (s.substr(i, 3) == "#.#"); } cout << ans << '\n'; return 0; }
B - Traveling Takahashi Problem (abc375 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; vector<array<int, 2>> p(n + 2); p[0] = {0, 0}; for (int i = 1; i < n + 1; i++) { cin >> p[i][0] >> p[i][1]; } p[n + 1] = {0, 0}; double ans = 0; auto dist = [](array<int, 2> a, array<int, 2> b) { return sqrt(1ll * (a[0] - b[0]) * (a[0] - b[0]) + 1ll * (a[1] - b[1]) * (a[1] - b[1])); }; for (int i = 1; i < p.size(); ++i) { ans += dist(p[i], p[i - 1]); } cout << fixed << setprecision(10) << ans << '\n'; return 0; }
C - Spiral Rotation (abc375 C)
题目大意
给定一个的正方形,对于 ,依次进行如下操作:
- 以为一角的 的正方形顺时针旋转度。
输出最终的矩形。
解题思路
注意到旋转的区间是往中间收缩的,因为旋转操作具有结合性和周期性(旋转次等于没旋转),所以依次遍历该正方形的每个元素,可以求出它进行了多少次 顺时针旋转 度,然后对 取余后,再进行对应次数的顺时针旋转即可。
其实暴力的复杂度是,虽然,但貌似还是不能过。
神奇的代码
#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; vector<string> s(n); for (auto& i : s) cin >> i; auto tr = [](int x, int y, int n) { return make_pair(y, n - 1 - x); }; auto tr_cnt = [&](int x, int y, int n, int c) { for (int i = 0; i < c; ++i) { tie(x, y) = tr(x, y, n); } return make_pair(x, y); }; vector<string> t = s; for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { int cnt = min({i, j, n - 1 - i, n - 1 - j}) + 1; auto [x, y] = tr_cnt(i, j, n, cnt % 4); t[x][y] = s[i][j]; } } for (auto i : t) cout << i << '\n'; return 0; }
D - ABA (abc375 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); string s; cin >> s; array<int, 26> cnt{}; array<LL, 26> sum{}; LL ans = 0; for (int i = 0; i < s.size(); ++i) { int c = s[i] - 'A'; ans += 1ll * cnt[c] * (i - 1) - sum[c]; cnt[c]++; sum[c] += i; } cout << ans << '\n'; return 0; }
E - 3 Team Division (abc375 E)
题目大意
三个数组,操作为,花费的代价,将一个数从一个数组移动到另一个数组。
问最小的代价,使得三个数组的和相等,或告知不可行。
数组和。
解题思路
考虑两个数组的简化版。
考虑朴素搜索,即依次考虑每个数,应该移动还是不移动。其复杂度显然是。
因为状态是包含了每个数的数组位置,它过于冗余了,考虑到我们要求的答案,我们可以仅保留一个简化的状态即可:
- 一个数组的和
知道了一个数组的和,对于当前数,移动还是不移动,其带来的影响都可以进行转移,最后也能直接得到答案。
因为总数不变,所以知道了一个数组的和,另一个数组的和其实也知道了,即设 表示考虑了前 个数,第一个数组和为 的最小移动代价。对于一个数,考虑移动或不移动,对 的影响转移即可。这样的复杂度即为
而这里有三个数组,按照同样的思路,知道了前两个数组的和,第三个数组的和也确定了,即设表示考虑了前 个数,第一个数组和为 ,第二个数组和为的最小移动代价,转移仍然是对于一个数,考虑移动到哪个数组,或者不移动即可。这样的时间复杂度为
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int inf = 1e9 + 7; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; array<vector<int>, 3> p{}; int sum = 0; for (int i = 0; i < n; i++) { int a, b; cin >> a >> b; --a; p[a].push_back(b); sum += b; } if (sum % 3 != 0) cout << -1 << '\n'; else { vector<vector<int>> dp(sum + 1, vector<int>(sum + 1, inf)); dp[accumulate(p[0].begin(), p[0].end(), 0)] [accumulate(p[1].begin(), p[1].end(), 0)] = 0; for (auto& i : p[0]) { auto dp2 = dp; for (int j = 0; j <= sum; j++) { for (int k = 0; k <= sum; k++) { if (j >= i) dp2[j - i][k] = min(dp2[j - i][k], dp[j][k] + 1); if (j >= i && k + i <= sum) dp2[j - i][k + i] = min(dp2[j - i][k + i], dp[j][k] + 1); } } dp.swap(dp2); } for (auto& i : p[1]) { auto dp2 = dp; for (int j = 0; j <= sum; j++) { for (int k = 0; k <= sum; k++) { if (k >= i) dp2[j][k - i] = min(dp2[j][k - i], dp[j][k] + 1); if (k >= i && j + i <= sum) dp2[j + i][k - i] = min(dp2[j + i][k - i], dp[j][k] + 1); } } dp.swap(dp2); } for (auto& i : p[2]) { auto dp2 = dp; for (int j = 0; j <= sum; j++) { for (int k = 0; k <= sum; k++) { if (k + i <= sum) dp2[j][k + i] = min(dp2[j][k + i], dp[j][k] + 1); if (j + i <= sum) dp2[j + i][k] = min(dp2[j + i][k], dp[j][k] + 1); } } dp.swap(dp2); } int target = sum / 3; if (dp[target][target] == inf) { dp[target][target] = -1; } cout << dp[target][target] << '\n'; } return 0; }
F - Road Blocked (abc375 F)
题目大意
给定一张无向图,维护以下两种操作:
1 x
,删去第条边2 u v
,问的最短路长度
其中操作 最多 次。
解题思路
加边往往比删边好做,注意到询问没有强制在线,因此可以倒着考虑操作,这样删边操作就变成加边操作了。
因为点数,先用 花 求出任意两点的最短路,这样操作 就可以 回答,然后考虑操作 加边后,怎么更新这个两点的最短路。
考虑加的边 对任意两点 的最短路的影响,其实就两种情况:
- 的最短路不经过
- 的最短路经过
在加边之前,的值就属于第一种情况,现在加边后, 就是这两种情况的最小值,因此只要和第二种情况取个最小值,这样就变成 加上边 后的值了。
而第二种情况的距离即为 或 。
加一边,更新一次的复杂度为 ,因为操作 不超过 ,因此总的时间复杂度为 。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const LL inf = 1e18; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m, q; cin >> n >> m >> q; vector<vector<LL>> dis(n, vector<LL>(n, inf)); vector<array<int, 3>> edges(m); for (int i = 0; i < m; ++i) { int u, v, w; cin >> u >> v >> w; --u, --v; dis[u][v] = dis[v][u] = min(dis[u][v], (LL)w); edges[i] = {u, v, w}; } for (int i = 0; i < n; ++i) dis[i][i] = 0; vector<array<int, 3>> query(q); for (auto& [op, x, y] : query) { cin >> op >> x; if (op == 1) { --x; auto [u, v, w] = edges[x]; dis[u][v] = dis[v][u] = inf; } else { cin >> y; --x, --y; } } for (int k = 0; k < n; ++k) for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]); vector<LL> ans; for (int i = q - 1; i >= 0; --i) { auto [op, x, y] = query[i]; if (op == 1) { auto [u, v, w] = edges[x]; dis[u][v] = dis[v][u] = min(dis[u][v], (LL)w); for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) { dis[i][j] = min(dis[i][j], dis[i][u] + dis[u][v] + dis[v][j]); dis[i][j] = min(dis[i][j], dis[i][v] + dis[v][u] + dis[u][j]); } } else { ans.push_back((dis[x][y] == inf ? -1 : dis[x][y])); } } reverse(ans.begin(), ans.end()); for (auto x : ans) cout << x << '\n'; return 0; }
G - Road Blocked 2 (abc375 G)
题目大意
给定一张无向图,对于每一条边,回答以下询问:
- 删去该边后,的最短路长度是否发生变化。
解题思路
题意就是问一条边是不是最短路的必经之边,换句话说就是一条边是否被所有最短路径经过。
由于可能有多条最短路,我们先求出每条边,是否被一条最短路经过。
如果一条边 满足: 或者 ,这说明该边被一条最短路径经过。
但怎么说该边被所有的最短路径经过呢?
如果的最短路径有 条,而 的最短路径有 条, 的最短路径有 条,由乘法原理,且经过 的路径就有 条,而如果有,那这就说明该边是必经边。
因此分别从点和点 进行 ,求出 和 表示的最短路径长度和数量, 的最短路径长度和数量,对于任意一条边,如果满足二者任意其一:
- 且
- 且
则该边是最短路必经边。
其实根据一条边是否被最短路经过,建立一张最短路图,这些必经边就是所谓的割边,即删去该边后, 不连通。
注意 可能会非常大,甚至会超过 , 而此处我们只需判断乘积是否相等,可以取模。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const LL inf = 1e18; const int mo = 1e9 + 7; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; vector<vector<array<int, 2>>> edge(n); vector<array<int, 3>> e(m); for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; --u, --v; edge[u].push_back({v, w}); edge[v].push_back({u, w}); e[i] = {u, v, w}; } auto dijkstra = [&](int s) { vector<LL> dis(n, inf); vector<LL> cnt(n, 0); dis[s] = 0; cnt[s] = 1; priority_queue<array<LL, 2>, vector<array<LL, 2>>, greater<array<LL, 2>>> team; team.push({0, s}); while (!team.empty()) { auto [d, u] = team.top(); team.pop(); if (dis[u] < d) continue; for (auto [v, w] : edge[u]) { if (dis[v] > dis[u] + w) { dis[v] = dis[u] + w; team.push({dis[v], v}); cnt[v] = cnt[u]; } else if (dis[v] == dis[u] + w) { cnt[v] += cnt[u]; if (cnt[v] >= mo) cnt[v] -= mo; } } } return make_pair(dis, cnt); }; auto [dis1, cnt1] = dijkstra(0); auto [dis2, cnt2] = dijkstra(n - 1); vector<int> ans(m, 0); for (int i = 0; i < m; ++i) { auto& [u, v, w] = e[i]; if (dis1[u] + w + dis2[v] == dis1[n - 1] && cnt1[u] * cnt2[v] % mo == cnt1[n - 1] || dis1[v] + w + dis2[u] == dis1[n - 1] && cnt1[v] * cnt2[u] % mo == cnt1[n - 1]) { cout << "Yes" << '\n'; } else { cout << "No" << '\n'; } } return 0; }
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/18461661
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步