Processing math: 100%

AtCoder Beginner Contest 350

A - Past ABCs (abc350 A)

题目大意

给定一个形如 ABCXXX的字符串。

XXX是否是001349之间,且不能是 316

解题思路

将后三位转换成数字后判断即可。

神奇的代码
a = int(input().strip()[3:])
if a >= 1 and a <= 349 and a != 316:
print("Yes")
else:
print("No")


B - Dentist Aoki (abc350 B)

题目大意

给定n01序列。

进行q次操作,每次操作反转某一位上的 01

问最后 1的个数。

解题思路

反转操作的复杂度是O(1),因此直接模拟反转即可,最后求和得到答案。

神奇的代码
#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, q;
cin >> n >> q;
vector<int> a(n, 1);
while (q--) {
int x;
cin >> x;
--x;
a[x] ^= 1;
}
int ans = accumulate(a.begin(), a.end(), 0);
cout << ans << '\n';
return 0;
}


C - Sort (abc350 C)

题目大意

给定一个1n的排序,通过最多n1次操作以下操作将其变得有序。

操作为,交换任意两个数。

输出任意可行的操作次数及其对应的操作步骤。

解题思路

i=1n,依次考虑将 i交换到第 i位。经过 n1次操作后则必定有序。

因此需要记录 pos[i]表示数字 i所在的位置,每次交换第i,pos[i] 位,就将i交换到第 i位,重复执行n1次即可。

神奇的代码
#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<int> pos(n);
vector<int> a(n);
for (int i = 0; i < n; i++) {
int x;
cin >> x;
--x;
pos[x] = i;
a[i] = x;
}
vector<array<int, 2>> ans;
for (int i = 0; i < n; i++) {
if (a[i] == i)
continue;
ans.push_back({i, pos[i]});
swap(a[i], a[pos[i]]);
swap(pos[a[i]], pos[a[pos[i]]]);
}
cout << ans.size() << '\n';
for (auto& p : ans) {
cout << p[0] + 1 << ' ' << p[1] + 1 << '\n';
}
return 0;
}


D - New Friends (abc350 D)

题目大意

给定一张无向图,若三点x,y,z,存在:

  • x,y有连边
  • y,z有连边
  • x,z无连边

则连边x,z

问最多能连多少次边。

解题思路

考虑最简单的一条链的情况1234,容易发现可以连的边有

  • 12,23 => 13
  • 13,34 => 14
  • 23,34 => 24

观察1的新增边的情况,会发现它可以和所有能到达的点连边,即最终情况下,一个连通块内的任意两点都会连边,即变成一张完全图。

因此BFS得到每个连通块的点数 cp和边数 ce,最终情况下该连通块会有 cp(cp1)2 条边,而已经有ce条(无向)边,因此可以连 cp(cp1)2ce条边。

所有连通块的连边次数相加即为答案。

神奇的代码
#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<int>> edge(n);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
--u, --v;
edge[u].push_back(v);
edge[v].push_back(u);
}
LL ans = 0;
vector<int> vis(n, 0);
for (int i = 0; i < n; ++i) {
if (vis[i])
continue;
queue<int> team;
team.push(i);
vis[i] = 1;
int cp = 1, ce = 0;
while (!team.empty()) {
int u = team.front();
team.pop();
for (auto v : edge[u]) {
++ce;
if (vis[v])
continue;
vis[v] = 1;
team.push(v);
cp++;
}
}
ans += 1ll * cp * (cp - 1) - ce;
}
ans /= 2;
cout << ans << '\n';
return 0;
}


E - Toward 0 (abc350 E)

题目大意

给定一个数字nx,y,a。通过两类操作,使得n变为 0

  • 操作一,花费代价x,使得 n=na
  • 操作二,花费代价y,掷骰子,等概率掷出16中的一个b,使得 n=nb

问最优情况下,最小期望花费。

解题思路

期望题,根据定义,当前的期望值是所有后继情况的期望值的概率加权。

dp[i]表示当前数字为 x,将其变为 0的最小期望花费。

边界条件很明显就是 dp[0]=0

虽然是期望,但它问的是最优情况下的最小花费,那就是一个决策最优问题,考虑我的决策是什么。

很显然,决策就是操作一还是操作二,如果我决定执行操作一,会有一个期望值,执行操作二,会有另一个期望值,这两个期望值取最小,就是我做出的最优决策。因此需要分别求出操作一和操作二的期望花费。

根据定义,当前的期望值是所有后继情况的期望值的概率加权。

当我执行操作一后,后继情况只有一个,那就是dp[na],达到这个情况的概率是 1。因此操作一的期望花费cost1=dp[na]+x

当我执行操作二后,后继情况有6个:

  • dp[n1]
  • dp[n2]
  • dp[n3]
  • dp[n4]
  • dp[n5]
  • dp[n6]

到达每一个后继情况的概率都是16

根据期望定义,可以得到操作二的期望花费 dp[n]=6i=1dp[ni]6+y

但注意到i=1那一项是 dp[n],与左式是一样的,这会造成循环求值,这里我们将右边的 dp[n] 移到左边,合并同类项,就可以得到真正的cost2=6i=2dp[ni]5+65y

得到两个操作的期望花费cost1,cost2后,接下来就是做决策——取花费最小的,作为 dp[n]的值。这样是转移了。

虽然 nO(1018),但由于每次都至少/2,最多除以 log次就变成 0,总的状态数其实很少,只有O(log2log3log4log5log6),大概就4e7的数量级。

从上面的分析可以看出,期望dpdp之间的区别仅仅是计算转移代价时需要用到期望定义来算,最终还是根据不同操作之间的代价取最优,还是个决策问题。

神奇的代码
#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);
LL n;
int a, x, y;
cin >> n >> a >> x >> y;
map<LL, double> dp;
auto dfs = [&](auto dfs, LL u) -> double {
if (u == 0)
return 0.;
if (dp.find(u) != dp.end())
return dp[u];
double cost1 = dfs(dfs, u / a) + x;
double cost2 = 0;
for (int i = 2; i <= 6; i++) {
cost2 += dfs(dfs, u / i);
}
cost2 = cost2 / 5 + y * 6. / 5;
return dp[u] = min(cost1, cost2);
};
dfs(dfs, n);
cout << fixed << setprecision(10) << dp[n] << '\n';
return 0;
}


F - Transpose (abc350 F)

题目大意

给定一个括号序列s,长度为n,其中也包括大小写字母。

依次处理每个匹配的括号里的字符,将其左右颠倒,并将大小写字母变换。

问最终的字符串。

解题思路

考虑朴素的做法,进行括号匹配,然后处理括号内的字符串,容易发现最坏情况下复杂度是O(n2),比如((((((((((asjigjiogjwifjwefckfj))))))))))

注意到同一个字符块执行两次上述变换后相当于没变换,从上述的最坏情况下可以启示我们,我们不需要实际进行变换,仅仅将这一块字符串看作整体,然后打个标记。就跟线段树的懒标记差不多。

比如(((as)(sf))(ef)),我们先将每一块字符串看作整体,从 0开始标号,则变为(((0)(1))(2)),然后处理每对匹配的括号,比如变成了((34)(2)),然后处理 (34),这里我们就不给34重复打标记,因为那样的复杂度可能会变回原来的O(n2),而是将 34看成一个整体 5,在5上打标记,最后输出时再传递给 34。变成 (5(2)),然后是(56),然后是7

就跟线段树的思想一样,我们会发现345,26,567,它们的关系形成了一棵树的关系(但可能不是二叉树),然后打标记都是在父亲节点打标记,最后输出时,从根节点开始遍历,不断下放标记,下放到叶子时,再根据标记决定是否倒序输出即可。

神奇的代码
#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;
s = "(" + s + ")";
vector<string> info;
info.push_back("(");
info.push_back(")");
string t;
vector<int> tr;
for (auto& i : s) {
if (i == '(' || i == ')') {
if (!t.empty()) {
tr.push_back(info.size());
info.push_back(t);
}
t.clear();
if (i == '(')
tr.push_back(0);
else
tr.push_back(1);
} else
t += i;
}
vector<vector<int>> edge(info.size());
stack<int> st;
for (auto i : tr) {
if (i == 0) { // (
st.push(0);
} else if (i == 1) { // )
int fa = info.size();
info.push_back("");
edge.push_back(vector<int>());
while (!st.empty() && st.top() != 0) {
edge[fa].push_back(st.top());
st.pop();
}
st.pop();
st.push(fa);
} else {
st.push(i);
}
}
auto inverse = [](string& s) {
reverse(s.begin(), s.end());
for (auto& i : s) {
if (islower(i))
i = toupper(i);
else
i = tolower(i);
}
};
auto dfs = [&](auto dfs, int u, int d) -> void {
if (edge[u].empty()) { // leaf
if (d)
inverse(info[u]);
cout << info[u];
return;
}
if (d)
reverse(edge[u].begin(), edge[u].end());
for (auto v : edge[u]) {
dfs(dfs, v, d ^ 1);
}
};
dfs(dfs, info.size() - 1, 1);
return 0;
}


G - Mediator (abc350 G)

题目大意

给定n个点,执行下列 q次操作,分两种,强制在线。

  • 1 u v,连无向边uv,连边之前保证u,v不连通。
  • 2 u v,询问是否有一个点x,使得uxv

解题思路

<++>

神奇的代码


posted @   ~Lanly~  阅读(477)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 为DeepSeek添加本地知识库
· 精选4款基于.NET开源、功能强大的通讯调试工具
· DeepSeek智能编程
· 大模型工具KTransformer的安装
· [计算机/硬件/GPU] 显卡
点击右上角即可分享
微信分享提示