2024-01-27 22:32阅读: 282评论: 5推荐: 1

AtCoder Beginner Contest 338

A - Capitalized? (abc338 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);
string s;
cin >> s;
auto t = s;
t[0] = toupper(t[0]);
transform(t.begin() + 1, t.end(), t.begin() + 1, ::tolower);
if (s == t)
cout << "Yes" << endl;
else
cout << "No" << endl;
return 0;
}


B - Frequency (abc338 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);
array<int, 26> cnt{};
string s;
cin >> s;
for (auto& i : s) {
cnt[i - 'a']++;
}
int maxx = ranges::max(cnt);
for (int i = 0; i < 26; ++i)
if (cnt[i] == maxx) {
cout << char(i + 'a') << '\n';
break;
}
return 0;
}


C - Leftover Recipes (abc338 C)

题目大意

现有n种素材各 qi个。

制作两种物品,物品A需要素材各 ai个,物品 B需要素材各 bi个。

问制作出来的物品数量的最大值。

解题思路

注意到qi106

那么一种物品制作出来的最大数量只有 106

因此我们可以直接枚举制作的物品 A的数量,然后用剩下的素材看看能制作多少个物品 B

时间复杂度是 O(106n)

神奇的代码
#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<LL> q(n), a(n), b(n);
for (auto& x : q)
cin >> x;
for (auto& x : a)
cin >> x;
for (auto& x : b)
cin >> x;
LL ans = 0;
for (int i = 0; i <= 1000000; ++i) {
vector<LL> left(n);
for (int j = 0; j < n; ++j) {
left[j] = q[j] - 1ll * i * a[j];
}
if (ranges::min(left) < 0) {
continue;
}
LL r = 1e9;
for (int j = 0; j < n; ++j) {
if (b[j])
r = min(r, left[j] / b[j]);
}
ans = max(ans, r + i);
}
cout << ans << '\n';
return 0;
}


D - Island Tour (abc338 D)

题目大意

给定一个n个点的环,然后依次访问m个点 v1,v2,...,vm

现问删去一条边,求依次访问这些点的距离(边数和)的最小值。

解题思路

一个朴素的O(n2)做法即为枚举删去的边 ii+1,然后依次访问这些点求距离,取最小值。

考虑如何优化,上述做法的时间复杂度,一个花在了枚举删去的边 O(n),一个花在了计算距离的 O(n)

按顺序枚举删去的边,看看后者能否快速从上一个的答案得到。

通过画图可以发现可行。如下图所示。

假设一开始删除 n1的边,求距离,即 i=1m1|vi+1vi|

然后考虑删除 12的边,发现 13的路径会变化,长度从原来的 315(31)。而其他的边比如 24的路径没有变化。

而当删除 34的边时,原来的 13的路径再度发生变化,从 5(31)变回了 31

综上观察可得,对于原来的路径 ij(i<j),当我们删除边 ii+1时,该路径会发生变化,而当删除边 jj+1时,该路径会再度发生变化。

因此当我们删边从 i1i变成 ii+1时,对于路径 xy(x<y)中,只有所有 x=iy=i的路径会发生变化,我们直接更新这些路径的距离即可。其他路径则不会发生变化。

由于每条边只会被更新两次,因此总的时间复杂度是 O(n+m)

神奇的代码
#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<int> x(m);
for (auto& i : x) {
cin >> i;
--i;
}
vector<array<int, 3>> edge(m - 1);
for (int i = 0; i < m - 1; ++i) {
int u = x[i], v = x[i + 1];
if (u > v)
swap(u, v);
edge[i] = {u, v, v - u};
}
LL sum = 0;
for (auto& e : edge) {
sum += e[2];
}
LL ans = sum;
vector<vector<int>> l(n), r(n);
for (int i = 0; i < m - 1; ++i) {
l[edge[i][0]].push_back(i);
r[edge[i][1]].push_back(i);
}
for (int i = 0; i < n; ++i) {
for (auto id : l[i]) {
sum -= edge[id][2];
edge[id][2] = n - edge[id][2];
sum += edge[id][2];
}
for (auto id : r[i]) {
sum -= edge[id][2];
edge[id][2] = n - edge[id][2];
sum += edge[id][2];
}
ans = min(ans, sum);
}
cout << ans << '\n';
return 0;
}


E - Chords (abc338 E)

题目大意

n个点的圆环,给定m条线段,端点不重合,问是否有交点。

解题思路

考虑枚举线段[l,r],我们需要判断是否有线段与其相交,就需要:

  • 判断所有左端点在[l+1,r1]的线段的右端点的最大值是否大于r
  • 判断所有右端点在[l+1,r1]的线段的左端点的最小值是否小于l

对于第一个判断,则对线段的左端点排序,那上述判断就是一个关于右端点的区间最值问题,因为是多次查询而无修改,则用ST维护即可。这个区间范围直接通过关于左端点对lr的二分得到。
对于第二个判断,则对线段的右端点排序,那上述判断就是一个关于左端点的区间最值问题,因为是多次查询而无修改,则用ST维护即可。这个区间范围直接通过关于左端点对lr的二分得到。

时间复杂度为O(nlogn)

好像有更简洁的做法

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
template <typename T, class F = function<T(const T&, const T&)>>
class SparseTable {
public:
int n;
vector<vector<T>> mat;
F func;
SparseTable(const vector<T>& a, const F& f) : func(f) {
n = static_cast<int>(a.size());
int max_log = 32 - __builtin_clz(n);
mat.resize(max_log);
mat[0] = a;
for (int j = 1; j < max_log; j++) {
mat[j].resize(n - (1 << j) + 1);
for (int i = 0; i <= n - (1 << j); i++) {
mat[j][i] = func(mat[j - 1][i], mat[j - 1][i + (1 << (j - 1))]);
}
}
}
T get(int from, int to) const { // [from, to]
assert(0 <= from && from <= to && to <= n - 1);
int lg = 32 - __builtin_clz(to - from + 1) - 1;
return func(mat[lg][from], mat[lg][to - (1 << lg) + 1]);
}
};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<array<int, 2>> e(n);
for (auto& i : e) {
cin >> i[0] >> i[1];
if (i[0] > i[1])
swap(i[0], i[1]);
}
sort(e.begin(), e.end(),
[](const array<int, 2>& a, const array<int, 2>& b) {
return a[0] < b[0];
});
vector<int> s1(n), t1(n);
for (int i = 0; i < n; ++i) {
s1[i] = e[i][0];
t1[i] = e[i][1];
}
SparseTable<int> rb(t1, [&](int a, int b) { return max(a, b); });
auto ee = e;
sort(ee.begin(), ee.end(),
[](const array<int, 2>& a, const array<int, 2>& b) {
return a[1] < b[1];
});
vector<int> s2(n), t2(n);
for (int i = 0; i < n; ++i) {
s2[i] = ee[i][1];
t2[i] = ee[i][0];
}
SparseTable<int> ls(t2, [&](int a, int b) { return min(a, b); });
bool ok = false;
auto check = [&](int l, int r, auto& pos, auto& st) {
auto L = ranges::upper_bound(pos, l) - pos.begin();
auto R = ranges::lower_bound(pos, r) - pos.begin();
if (L >= R)
return false;
auto v = st.get(L, R - 1);
return v < l || v > r;
};
for (auto& i : e) {
if (check(i[0], i[1], s1, rb) || check(i[0], i[1], s2, ls)) {
ok = true;
break;
}
}
if (ok)
cout << "Yes" << endl;
else
cout << "No" << endl;
return 0;
}


F - Negative Traveling Salesman (abc338 F)

题目大意

给定一张图,边有边权,问访问所有点的最小边权和。边权有负,但无负环。多次访问算多次边权。

解题思路

一个朴素的做法就是状压DP,设 dp[i][j]表示当前访问的点的状态为i,当前在点j的最小距离。

因为会有边权为负,直接DP的话(即两个for循环,ij),在转移时i不变的情况下,可能会得到更优的 dp[i][k](k<jfor,导致 原来dp[i][k]往后更新的状态不是最优的。

这跟用BFS求最短距离遇到的问题一样,需仿照dijkstra那样,用一个优先队列来维护 DP顺序,从dp[i][j]最小的状态 (i,j)往后转移,或者像SPFA那样,通过多次进队解决,由于n只有 20,跑一次 floyd也行。

考虑其复杂度,状态数已经是O(2nn)1e7的级别了,再加上 优先队列的 log复杂度,第一次尝试时不出意料超时了。

考虑优化,注意到原来的两层循环状态(i,j),可能转移到 (i|(1<<k),k)(i,k) ,其中第二维大小关系不是固定的,但第一维要么更大,要么不变,而我们使用 dijkstra式的方式维护更新时,主要是防止i不变的转移造成的重复更新。

原来是直接维护状态(i,j)的转移顺序,状态数达到 O(2nn),但转移的第一维有明显的方向性,因此我们可以对第一维一层一层的求解,在每一层的求解使用优先队列维护转移。这样优先队列里的状态数降为O(n)n只有 20就基本没什么耗时,最后的复杂度就是O(2nnlogn)

神奇的代码
#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, 2>>> edge(n);
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
--u, --v;
edge[u].push_back({v, w});
}
int up = (1 << n);
vector<vector<int>> dp(up, vector<int>(n, INT_MAX));
for (int i = 0; i < n; ++i) {
dp[1 << i][i] = 0;
}
for (int i = 0; i < up; ++i) {
priority_queue<array<int, 3>, vector<array<int, 3>>,
greater<array<int, 3>>>
pq;
for (int j = 0; j < n; ++j) {
if (dp[i][j] != INT_MAX)
pq.push({dp[i][j], i, j});
}
while (!pq.empty()) {
auto [d, s, u] = pq.top();
pq.pop();
if (dp[s][u] < d)
continue;
for (auto [v, w] : edge[u]) {
if (dp[s | (1 << v)][v] > dp[s][u] + w) {
dp[s | (1 << v)][v] = dp[s][u] + w;
if ((s | (1 << v)) == s)
pq.push({dp[s | (1 << v)][v], s | (1 << v), v});
}
}
}
}
int ans = INT_MAX;
for (int i = 0; i < n; ++i) {
ans = min(ans, dp[(1 << n) - 1][i]);
}
if (ans == INT_MAX) {
cout << "No" << '\n';
} else
cout << ans << '\n';
return 0;
}


G - evall (abc338 G)

题目大意

给定一个字符串,包含数字和+。问所有的子串的表达式的值。若表达式不合法则其值为0

解题思路

<++>

神奇的代码


本文作者:~Lanly~

本文链接:https://www.cnblogs.com/Lanly/p/17992290

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ~Lanly~  阅读(282)  评论(5编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
历史上的今天:
2022-01-27 Atcoder Beginner Contest 236 G Good Vertices
2022-01-27 Atcoder Beginner Contest 236 F Spices
2022-01-27 Atcoder Beginner Contest 236 E Average and Median
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.