AtCoder Beginner Contest 383

省流版
  • A. 模拟加水漏水即可
  • B. 枚举两个加湿器的位置,然后统计加湿的单元格数量即可
  • C. 从每个加湿器进行 BFS 即可
  • D. 考虑因子个数的计算,分情况枚举质因数即可
  • E. 考虑f函数的求法,从小到大加边,考虑每条边对答案的贡献即可
  • F. 对颜色排序,在01背包的基础上,新增一个不同颜色时的转移,维护颜色的最后一次出现的位置即可

A - Humidifier 1 (abc383 A)

题目大意

加湿器初始无水,但一旦有水,单位时间内水量就会减少 1 升。

现给定 N 次加水操作,第 i 次加水发生在时间 Ti ,加了 Vi 升水。问最后一次加完后加湿器中剩余的水量。

解题思路

时刻最多只有 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 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)

题目大意

给定包含.#的二维网格,选择两个.放置加湿器,使得加湿器的加湿的单元格数量最大。

当且仅当单元格与至少一个加湿器的曼哈顿距离 D 在内时,该单元格被加湿。 (i,j)(i,j) 之间的曼哈顿距离定义为 |ii|+|jj|

解题思路

枚举两个加湿器的位置,然后统计加湿的单元格数量。时间复杂度为 O((HW)2)

神奇的代码
#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×W 的网格,"#"为墙壁;"."为地板;"H"为放置了加湿器。

如果从至少一个加湿器单元格向上、向下、向左或向右移动最多 D 次而不经过墙壁,则该单元格被视为加湿单元格。

求加湿地板单元格的数量。

解题思路

上述移动方式,即从每个加湿器单元格进行 BFS ,直到移动次数到达 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 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)

题目大意

求恰好有 9 个因子的不大于 N 的正整数的个数。

解题思路

将一个数质因数分解,设 n=p1a1×p2a2××pkak ,则 n 的因子个数为 (a1+1)×(a2+1)××(ak+1)

因此,如果一个数的因子个数为 9 ,则其质因数分解后,只能有两种情况:

  • p12×p22 ,此时 a1=a2=2
  • p18 ,此时 a1=8

由于 N4e12,因此对于情况一,p1p22e6,因此可以枚举 p1p2 ,然后判断是否满足条件,其时间复杂度不会超过 O(2e6)

而情况二,同样直接枚举 p1 ,然后判断是否满足条件即可。

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

题目大意

给定一张无向图,边有边权。给定两个长度为 K 的序列: (A1,A2,,AK)(B1,B2,,BK)

将序列 B 自由排列,使 i=1Kf(Ai,Bi) 最小。其中 f(x,y) 为从顶点 x 到顶点 y 的路径的最小路径权重。

一条路径的权重定义为路径中一条边的最大权重。

解题思路

考虑 f(x,y) 的定义,因为一条路径的权重定义为路径中一条边的最大权重,而两点的路径有很多条,我们要找最小权重的那条。

怎么求 f(x,y)呢?我们可以考虑加边,从边权最小的边开始加,这样,当两个点突然位于同一个连通块时(即有路径连通时),就说明这个边权就是这两个点路径的必经边,而由于我们是从小到大加边,所以这个边权就是这两个点路径的最小权重。
you

因此,我们可以将边按权重从小到大排序,然后依次加边,对于每条边,如果将两个不同的连通块合并,那么这两个连通块里的 AiBi 就可以连通,对应的连通点对 (Ai,Bi) 数量就是该边权对答案的贡献。所有的边权对答案的贡献求和即为答案。

我们可以用并查集来维护连通性。

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

题目大意

给定 N 种商品,每种商品有价格 Pi ,效用值 Ui ,颜色 Ci

选一些商品,使得总价不超过 X ,满意度最大化。满意度定义为 S+T×K ,其中 S 是所选商品的效用总和, T 是所选商品中不同颜色的数量, K 是一个给定的常数。

解题思路

如果没有颜色,那么这是一个经典的 01 背包问题。设 dp[i][j] 表示前 i 种商品,总价不超过 j 的情况下的最大效用值。

而颜色在这里会多了额外贡献。维护已经选了什么颜色的复杂度是指数级别的。我们可以考虑对商品按照颜色进行排序。

假设商品颜色为 1,1,1,2,2,2,...,比如我们在考虑第 5 个商品,它可以从 dp[4]转移过来,这和正常的 01 背包没有区别。同时,它也可以从dp[3]转移过来,但此时会有一个额外的 k的贡献,因为这是第一次选择颜色 2的商品。

因此对于当前的商品,我们额外维护一个位置,该位置是上一个颜色最后一次出现的位置last,这样在原来的 01 背包的基础上,再加一个从 dp[last]转移过来的状态即可。

由于转移时第一维仅依赖上一次,所以代码里将第一维的维滚动压缩掉,同时用 dpc 数组来维护 dp[last]的值。时间复杂度为 O(NX)

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

题目大意

问题陈述

有一行 N 单元格。每个单元格包含一个整数 Ai

NK 块瓷砖,每块瓷砖可以覆盖 K 个连续的单元格。

对于每一个 i=1,,NK 解决下面的问题:

  • 当恰好放置 i 个瓷砖,且相互不重叠,求被覆盖单元格中数字和的最大值。

解题思路

<++>

神奇的代码


posted @   ~Lanly~  阅读(75)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
历史上的今天:
2019-12-14 Codeforces Round #606 (Div. 2, based on Technocup 2020 Elimination Round 4)
点击右上角即可分享
微信分享提示