AtCoder Beginner Contest 383
省流版
- A. 模拟加水漏水即可
- B. 枚举两个加湿器的位置,然后统计加湿的单元格数量即可
- C. 从每个加湿器进行 即可
- D. 考虑因子个数的计算,分情况枚举质因数即可
- E. 考虑函数的求法,从小到大加边,考虑每条边对答案的贡献即可
- F. 对颜色排序,在背包的基础上,新增一个不同颜色时的转移,维护颜色的最后一次出现的位置即可
A - Humidifier 1 (abc383 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; cin >> n; int w = 0; int la = 0; while (n--) { int t, x; cin >> t >> x; w = max(0, w - t + la); la = t; w += x; } cout << w << '\n'; return 0; }
B - Humidifier 2 (abc383 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 h, w, d; cin >> h >> w >> d; vector<string> s(h); for (auto& i : s) cin >> i; vector<array<int, 2>> pos; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { if (s[i][j] == '.') { pos.push_back({i, j}); } } } auto inner = [&](int x, int y, int z) -> bool { auto [x1, y1] = pos[z]; return abs(x1 - x) + abs(y1 - y) <= d; }; auto solve = [&](int x, int y) -> int { int cnt = 0; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { cnt += s[i][j] == '.' && (inner(i, j, x) || inner(i, j, y)); } } return cnt; }; int ans = 0; for (int i = 0; i < pos.size(); i++) { for (int j = i + 1; j < pos.size(); j++) { ans = max(ans, solve(i, j)); } } cout << ans << '\n'; return 0; }
C - Humidifier 3 (abc383 C)
题目大意
给定一个 的网格,"#"为墙壁;"."为地板;"H"为放置了加湿器。
如果从至少一个加湿器单元格向上、向下、向左或向右移动最多 次而不经过墙壁,则该单元格被视为加湿单元格。
求加湿地板单元格的数量。
解题思路
上述移动方式,即从每个加湿器单元格进行 ,直到移动次数到达 ,途中经过的地板单元格均为加湿单元格。
神奇的代码
#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 h, w, d; cin >> h >> w >> d; vector<string> s(h); for (auto& i : s) cin >> i; queue<array<int, 2>> team; int ans = 0; vector<vector<int>> dis(h, vector<int>(w, h * w + 8)); for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { if (s[i][j] == 'H') { team.push({i, j}); dis[i][j] = 0; ++ans; } } } array<int, 2> dir[] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; while (!team.empty()) { auto [x, y] = team.front(); team.pop(); for (auto [dx, dy] : dir) { int nx = x + dx, ny = y + dy; if (nx >= 0 && nx < h && ny >= 0 && ny < w && s[nx][ny] != '#' && dis[x][y] + 1 <= d && dis[nx][ny] > dis[x][y] + 1) { dis[nx][ny] = dis[x][y] + 1; team.push({nx, ny}); ++ans; } } } cout << ans << '\n'; return 0; }
D - 9 Divisors (abc383 D)
题目大意
求恰好有 个因子的不大于 的正整数的个数。
解题思路
将一个数质因数分解,设 ,则 的因子个数为 。
因此,如果一个数的因子个数为 ,则其质因数分解后,只能有两种情况:
- ,此时 ;
- ,此时 。
由于 ,因此对于情况一,,因此可以枚举 和 ,然后判断是否满足条件,其时间复杂度不会超过 。
而情况二,同样直接枚举 ,然后判断是否满足条件即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const LL p_max = 2E6 + 100; LL pr[p_max], p_sz; void get_prime() { static bool vis[p_max]; for (int i = 2; i < p_max; i++) { if (!vis[i]) pr[p_sz++] = i; for (int j = 0; j < p_sz; j++) { if (pr[j] * i >= p_max) break; vis[pr[j] * i] = 1; if (i % pr[j] == 0) break; } } } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); get_prime(); LL n; cin >> n; LL half = ceil(sqrt(n)); LL ans = 0; for (int i = 0; i < p_sz; i++) { for (int j = i + 1; j < p_sz; j++) { if (pr[i] * pr[j] > half) break; ans += (pr[i] * pr[i] * pr[j] * pr[j] <= n); } LL tmp = 1; int up = 0; for (; up < 8; ++up) { tmp *= pr[i]; if (tmp > n) break; } ans += (up == 8); } cout << ans << '\n'; return 0; }
E - Sum of Max Matching (abc383 E)
题目大意
给定一张无向图,边有边权。给定两个长度为 的序列: 和 。
将序列 自由排列,使 最小。其中 为从顶点 到顶点 的路径的最小路径权重。
一条路径的权重定义为路径中一条边的最大权重。
解题思路
考虑 的定义,因为一条路径的权重定义为路径中一条边的最大权重,而两点的路径有很多条,我们要找最小权重的那条。
怎么求 呢?我们可以考虑加边,从边权最小的边开始加,这样,当两个点突然位于同一个连通块时(即有路径连通时),就说明这个边权就是这两个点路径的必经边,而由于我们是从小到大加边,所以这个边权就是这两个点路径的最小权重。
you
因此,我们可以将边按权重从小到大排序,然后依次加边,对于每条边,如果将两个不同的连通块合并,那么这两个连通块里的 和 就可以连通,对应的连通点对 数量就是该边权对答案的贡献。所有的边权对答案的贡献求和即为答案。
我们可以用并查集来维护连通性。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; class dsu { public: vector<int> p; vector<int> a; vector<int> b; int n; dsu(int _n) : n(_n) { p.resize(n); a.resize(n); b.resize(n); iota(p.begin(), p.end(), 0); fill(a.begin(), a.end(), 0); fill(b.begin(), b.end(), 0); } inline int get(int x) { return (x == p[x] ? x : (p[x] = get(p[x]))); } inline LL unite(int x, int y, int w) { x = get(x); y = get(y); LL ret = 0; if (x != y) { int c = min(a[x], b[y]); a[x] -= c; b[y] -= c; ret += 1ll * c * w; c = min(a[y], b[x]); a[y] -= c; b[x] -= c; ret += 1ll * c * w; p[x] = y; a[y] += a[x]; b[y] += b[x]; } return ret; } }; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m, k; cin >> n >> m >> k; vector<array<int, 3>> edge(m); for (auto& [u, v, w] : edge) { cin >> u >> v >> w; u--; v--; } dsu d(n); for (int i = 0; i < k; i++) { int x; cin >> x; d.a[x - 1]++; } for (int i = 0; i < k; i++) { int x; cin >> x; d.b[x - 1]++; } vector<int> id(m); iota(id.begin(), id.end(), 0); sort(id.begin(), id.end(), [&](int i, int j) { return edge[i][2] < edge[j][2]; }); LL ans = 0; for (auto i : id) { auto [u, v, w] = edge[i]; ans += d.unite(u, v, w); } cout << ans << '\n'; return 0; }
F - Diversity (abc383 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, k; cin >> n >> x >> k; vector<array<int, 3>> a(n); for (auto& [p, u, c] : a) cin >> p >> u >> c; vector<int> id(n); iota(id.begin(), id.end(), 0); sort(id.begin(), id.end(), [&](int i, int j) { return a[i][2] < a[j][2]; }); vector<LL> dp(x + 1, 0); vector<LL> dpc(x + 1, 0); int last = 0; for (auto i : id) { auto [p, u, c] = a[i]; if (last != c) dpc = dp; vector<LL> dp2 = dp; for (int j = 0; j <= x; j++) { if (j >= p) { dp2[j] = max(dp2[j], dp[j - p] + u); dp2[j] = max(dp2[j], dpc[j - p] + u + k); } } last = c; dp.swap(dp2); } LL ans = *max_element(dp.begin(), dp.end()); cout << ans << '\n'; return 0; }
G - Bar Cover (abc383 G)
题目大意
问题陈述
有一行 单元格。每个单元格包含一个整数 。
有 块瓷砖,每块瓷砖可以覆盖 个连续的单元格。
对于每一个 解决下面的问题:
- 当恰好放置 个瓷砖,且相互不重叠,求被覆盖单元格中数字和的最大值。
解题思路
<++>
神奇的代码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
2019-12-14 Codeforces Round #606 (Div. 2, based on Technocup 2020 Elimination Round 4)