AtCoder Beginner Contest 399 ABCDEF 题目解析

news/2025/4/2 19:07:14/文章来源:https://www.cnblogs.com/stelayuri/p/18800119

A - Hamming Distance

题意

给定两个长度均为 \(N\) 的字符串 \(S\)\(T\),求总共有多少个位置不同。

思路

直接输入字符串后逐位判断即可。

代码

int n;
string s, t;
cin >> n >> s >> t;
int ans = 0;
for(int i = 0; i < n; i++)ans += (s[i] != t[i]);
cout << ans;

B - Ranking with Ties

题意

已知有 \(N\) 人参加了一场比赛,第 \(i\) 个人的得分是 \(P_i\)

求出每个人的排名,得分越高排名越靠前。

得分相同的排名也相同,但假设得分相同的人数为 \(k\),他们的排名均为 \(r\),那么下一名的排名应当是 \(r + k\)

思路一

按题意直接模拟即可。

代码一

#include<bits/stdc++.h>
using namespace std;int a[105], rk[105];int main()
{int n;cin >> n;for(int i = 1; i <= n; i++)cin >> a[i];int cur = 1; // 下一名的排名while(1){int mx = 0;for(int i = 1; i <= n; i++)if(rk[i] == 0) // 未确定排名mx = max(mx, a[i]);if(mx == 0)break;int cnt = 0; // 相同排名人数for(int i = 1; i <= n; i++)if(a[i] == mx){rk[i] = cur;cnt++;}cur += cnt;}for(int i = 1; i <= n; i++)cout << rk[i] << "\n";return 0;
}

思路二

明显一个人的排名等于“比他分数高的人数 \(+1\)”。

可以把所有人的成绩放一个新数组里,排序。

然后借助循环或者二分等方法求出比当前分数更高的分有多少个。

代码二

#include<bits/stdc++.h>
using namespace std;
int a[105], b[105];
int main()
{int n;cin >> n;for(int i = 1; i <= n; i++){cin >> a[i];b[i] = a[i];}sort(b + 1, b + n + 1);for(int i = 1; i <= n; i++){// upper_bound 找比 a[i] 大的最小位置 pos,数量即 n-pos+1int cnt = n - (upper_bound(b + 1, b + n + 1, a[i]) - b) + 1;cout << cnt + 1 << "\n";}return 0;
}

C - Make it Forest

题意

给定一张简单无向图,包含 \(N\) 个点与 \(M\) 条边。

问至少删除多少条边,可以使得该图成为森林图。

森林图:当一张简单无向图中不存在任何环时,才会被称作森林图。

思路

我们的目标是让图中的每一个连通块都变成一棵树。

由于树的性质是“点数 \(-1=\) 边数”,我们只需要对每个连通块都保留“点数 \(-1\)”条边即可。

因此只需要求出每个连通块的点数 \(n\) 与边数 \(m\),那么需要删除的边数就是 \(m - (n-1)\)。求和即为答案。

代码一

借助搜索算法找连通块内点数与边数。

#include<bits/stdc++.h>
using namespace std;int n, m;
vector<int> G[200005];bool vis[200005]; // 判断该点是否已被访问过int cnt, edges;
// 分别表示这一次搜索到的“总点数”以及“总度数”void dfs(int u, int fa)
{vis[u] = true;cnt++;edges += G[u].size(); // 加上该点的度数for(int &v : G[u]){if(v == fa)continue;if(vis[v])continue;dfs(v, u);}
}int main()
{cin >> n >> m;for(int i = 1; i <= m; i++){int u, v;cin >> u >> v;G[u].push_back(v);G[v].push_back(u);}int ans = 0;for(int i = 1; i <= n; i++)if(!vis[i]){cnt = edges = 0;dfs(i, 0);ans += edges / 2 - (cnt - 1); // 无向图的边数 = 总度数 / 2}cout << ans;return 0;
}

代码二

由于整张图总点数 \(N\) 已知,当所有点都在一个连通块内时,我们的目标就是把整张图变成一棵树,即边数 \(=N-1\)

但如果所有点并不在同一个连通块内,每多一个连通块,相当于在最终的森林图中又少了一条边。

如果我们能够求出图中有多少个连通块 \(K\),就相当于我们可以在 \(N-1\) 的基础上再减少 \(K-1\) 条边。

也就是说,我们只需要留下 \((N-1) - (K-1) = N-K\) 条边即可。

答案即 \(M-(N-K)\)

至于如何求连通块数量,并查集即可。

#include<bits/stdc++.h>
using namespace std;int n, m;
int fa[200005];int find(int p)
{return p == fa[p] ? p : fa[p] = find(fa[p]);
}void merge(int u, int v)
{fa[find(u)] = find(v);
}int main()
{int n, m;cin >> n >> m;for(int i = 1; i <= n; i++)fa[i] = i; // 并查集初始化for(int i = 1; i <= m; i++){int u, v;cin >> u >> v;merge(u, v);}int k = 0;for(int i = 1; i <= n; i++)if(i == find(i)) // 这是某个集合的根k++;cout << m - (n - k);return 0;
}

D - Switch Seats

题意

有一个长度为 \(2N\) 的数组 \(A=(A_1,A_2,\dots, A_{2N})\),其中 \(1, 2, \dots, N\) 的每一个正整数都在数组 \(A\) 中严格出现 \(2\) 次。

问有多少对 \((a, b)\) \((1 \le a \lt b \le N)\) 满足:

  • 一开始,\(A\) 数组中的两个 \(a\) 并不相邻,两个 \(b\)并不相邻
  • 任意进行一次或多次下面的操作,能够使得最终 \(A\) 数组中的两个 \(a\) 和两个 \(b\) 变成相邻。
    • 选择 \(A\) 数组中的某个 \(a\) 和某个 \(b\),让它们交换位置。

多组数据。

思路

首先,很明显对于每一对 \((a, b)\),能够操作的位置只有 \(4\) 个。可以发现题目中的操作只会进行一次。

如果一开始 \(a\)\(b\) 不相邻,最终又希望只通过交换某个 \(a\) 和某个 \(b\) 让它们变成相邻的,那么可以想到在一开始的数组 \(A\) 当中,第一个 \(a\) 和第一个 \(b\) 一定相邻,第二个 \(a\) 和第二个 \(b\) 也一定相邻。

也就是说只会是下面四种情况

...ab...ab...
...ab...ba...
...ba...ab...
...ba...ba...

于是我们便可以枚举任意两个相邻的数字 \((A_{i-1}, A_{i})\) \((2 \le i \le 2N)\),判断它们是否符合题意即可。

注意答案去重。

代码

#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;int a[400005];int pos[200005][2], cnt[200005];
// pos[i][0], pos[i][1] 记录 i 这个数字出现的第一个和第二个位置
// cnt[i] 表示此时已经找到了多少个数字 i
bool vis[200005];
// vis[i] 表示 i 这个数字一开始已经相邻,不可能成为答案void solve()
{int n;cin >> n;for(int i = 1; i <= n; i++){vis[i] = false;cnt[i] = 0;}for(int i = 1; i <= 2 * n; i++){cin >> a[i];pos[a[i]][cnt[a[i]]++] = i; // 将 a[i] 出现的位置记录if(a[i] == a[i - 1]) // 已相邻,标记vis[a[i]] = true;}set<pii> st;for(int i = 2; i <= 2 * n; i++){if(vis[a[i]] || vis[a[i-1]])continue;// 判断 (a[i-1], a[i]) 是否能成为答案int x = a[i], y = a[i-1];if(x > y)swap(x, y);if(abs(pos[x][0] - pos[y][0]) == 1&& abs(pos[x][1] - pos[y][1]) == 1) // 前两个位置相邻,后两个位置也相邻st.insert(pii(x, y)); // 集合辅助去重}cout << st.size() << "\n";
}int main()
{int T;cin >> T;while(T--)solve();return 0;
}

E - Replace

题意

给定两个长度均为 \(N\) 的仅由小写字母组成的字符串 \(S,T\)

判断是否能够对 \(S\) 进行任意次以下操作,将 \(S\) 变为 \(T\)

  • 选择两个不同的英文字母 \(x, y\),将 \(S\) 字符串中所有的 \(x\) 全部改成 \(y\)

思路

对于 \(S\)\(T\) 两个字符串的每一个不相同的位置 \(i\),我们的目标都是要把 \(S_i \rightarrow T_i\)

如果以 \(26\) 英文字母作为结点,我们可以根据上述关系建立一张有向图。

首先考虑最简单的情况,如果一开始两字符串完全相等,答案为 \(0\);除此之外,如果根据上述关系,我们发现所有 \(26\) 个英文字母都成为了其它字母的转换目标(即所有字母形成了一个环),根据题目给定的第四个样例可以得知,我们没法找出第 \(27\) 个字母来完成一个暂存的操作,所以此时答案为 \(-1\)

比较特殊的是,如果某个字母出现了多个转换目标,很明显是无解的,输出 \(-1\) 即可。

接下来分类讨论,这张有向图会有很多个连通块,我们只需要一个个连通块单独处理即可:

  • 如果一个连通块是一张有向无环图,明显我们就按照拓扑序一个个处理字母的变换即可,此时的操作数就是这张图的边数。
  • 如果一个连通块包含一个环,那么会出现以下两种情况:
    • 如果这个连通块只是单纯的一个环(也就是所有点都在环上),根据样例四,我们需要先把环中的某个字母变成一个没有用的字母,再依次处理环上每条关系,此时的操作数就是这个环的点数(或者边数)\(+1\)
    • 如果这个连通块中存在某些点不在环上,此时一定会出现环外的点指向环的情况,也就是说环上的某个点一定会出现入度 \(\gt 1\)的情况,此时我们只需要把环上的另外一个字母先改为环外这个字母,之后一起变成目标即可。在这种情况下的操作数还是等于连通块中的总边数。具体可以见下图:

总结,只有单个连通块所有点都在环上的情况需要特殊处理,操作次数需要额外 \(+1\)。其它情况的操作数均等于连通块内的总边数。

代码实现上,连通块的处理可以借助并查集,统计每个点的入度是否均为 \(1\) 即可。

注意处理自环的情况,自环不计入答案。

代码

#include<bits/stdc++.h>
using namespace std;int to[26];
// to[i] 记录 i 字母要变成的目标字母
bool vis[26];
// vis[i] 标记 i 是否被当作目标字母int ind[26];
// 记录入度int fa[26];
int find(int p)
{return p == fa[p] ? p : fa[p] = find(fa[p]);
}
void merge(int u, int v)
{fa[find(u)] = find(v);
}int main()
{memset(to, -1, sizeof to);int n;string a, b;cin >> n >> a >> b;if(a == b){cout << 0;return 0;}for(int i = 0; i < n; i++){int x = a[i] - 'a', y = b[i] - 'a';if(to[x] != -1 && to[x] != y){cout << -1;return 0;}to[x] = y; // 处理出所有转换关系vis[y] = true; // 标记 y 成为了其他字母的转换目标}bool haveTemp = false;for(int i = 0; i < 26; i++)if(vis[i] == false) // 只要有一个字母没有被成为目标,那么图中的所有环就都可以被解决{haveTemp = true;break;}if(haveTemp == false){// 此时所有字母都是其他字母的目标,不存在任何可以调整的字母cout << -1;return 0;}int ans = 0;for(int i = 0; i < 26; i++) // 并查集初始化fa[i] = i;for(int i = 0; i < 26; i++){if(to[i] != -1){ind[to[i]]++;if(to[i] != i) // 不是自环,答案 +1{ans++;merge(i, to[i]);}}}for(int i = 0; i < 26; i++){if(find(i) == i) // i 是某个集合的根{bool flag = true; // 判断这个集合是否是单纯的环int cnt = 0; // 求集合内点的数量for(int j = 0; j < 26; j++)if(find(j) == i){cnt++;if(ind[j] != 1)flag = false;}if(cnt > 1 && flag == true) // 是一个点数大于 1 的环ans++; // 多交换一次}}cout << ans;return 0;
}

F - Range Power Sum

题意

给定一个长度为 \(N\) 的数组 \(A = (A_1, A_2, \dots, A_N)\) 以及一个正整数 \(K\)

\(A\) 数组的每一段区间 \([l, r]\) \((1 \le l \le r \le N)\) 内的数字总和的 \(K\) 次方之和,输出对 \(998\,244\,353\) 取模。

思路

我们考虑数字是从前往后一个一个加入到 \(A\) 数组里的。

也就是说,对于每个数字 \(A_i\),我们只考虑以 \(i\) 作为右端点的所有区间 \([1, i], [2, i], \dots, [i,i]\) 对答案的贡献。至于如何快速求出这个贡献,我们考虑递推。

在此之前,根据二项式定理,我们要记住以下式子:

\[\begin{aligned} (a + b)^k &= \sum_{i = 0}^k C_k^i \cdot a^i \cdot b^{k-i} \\ &=C_k^0 \cdot a^0 \cdot b^k + C_k^1 \cdot a^1 \cdot b^{k-1} + \dots + C_k^k \cdot a^k \cdot b^0 \end{aligned} \]

然后我们考虑所有以 \(i\) 作为右端点的区间对答案的贡献,记作 \(T_{i, k}\),其中 \(k\) 表示当前的次方数。我们可以得到:

\[\begin{aligned} T_{i, k} &= (A_i)^k + (A_i + A_{i-1})^k + (A_i + A_{i-1} + A_{i-2})^k + \dots \\ &= (A_i)^k + [A_i + (A_{i-1})]^k + [A_i + (A_{i-1} + A_{i-2})]^k + \dots \\ &= (A_i)^k + [C_k^0 A_i^k (A_{i-1})^0 + C_k^1A_i^{k-1}(A_{i-1})^1 + \dots + C_k^kA_i^0(A_{i-1})^k] + \dots \\ &= (A_i)^k + C_k^0 A_i^k \cdot [(A_{i-1})^0 + (A_{i-1} + A_{i-2})^0 + \dots] + C_k^1 A_i^{k-1} \cdot [(A_{i-1})^1 + (A_{i-1} + A_{i-2})^1 + \dots] + \dots \\ &= (A_i)^k + C_k^0 A_i^k \cdot T_{i-1,0} + C_k^1 A_i^{k-1} \cdot T_{i-1,1} + C_k^2 A_i^{k-2} \cdot T_{i-1,2} + \dots \\ &= (A_i)^k + \sum_{j=0}^k C_k^j A_i^{k-j} T_{i-1,j} \end{aligned} \]

于是我们便能获得 \(T_{i, k}\) 的递推式。

根据题意,我们需要把每个右端点的 \(k\) 次方总和全部加起来,因此答案就是 \(T_{1, K} + T_{2, K} + \dots + T_{N,K}\)

时间复杂度 \(O(N\cdot K^2)\)

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 998244353;ll A[15];
// A[i] 表示当前正在处理的右端点这个数字的 i 次方
ll T[200005][15];
// T[i][k] 表示以 i 作为右端点时,所有区间总和的 k 次方之和
ll C[15][15];
// C[i][j] 表示组合数int main()
{int n, k;cin >> n >> k;for(int i = 0; i <= k; i++) // 杨辉三角求组合数{C[i][0] = C[i][i] = 1;for(int j = 1; j < i; j++)C[i][j] = (C[i-1][j] + C[i-1][j-1]) % mod;}A[0] = 1; // 单独处理 0 次方for(int i = 1; i <= n; i++){cin >> A[1]; // 输入当前这个数字,放在 1 次方的位置上for(int j = 2; j <= k; j++)A[j] = A[j - 1] * A[1] % mod; // 递推求出 2 到 k 次方for(int j = 0; j <= k; j++) // 求出每一个 T[i][j]{T[i][j] = A[j]; // 先把当前数字的 j 次方加进来for(int u = 0; u <= j; u++)T[i][j] = (T[i][j] + C[j][u] * A[j-u] % mod * T[i-1][u]) % mod;}}ll ans = 0;for(int i = 1; i <= n; i++)ans = (ans + T[i][k]) % mod;cout << ans;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/907931.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Solana编译失败探讨(OpenEuler RISC-V版)

Solana 是 2017 年由 Anatoly Yakovenko 创立的开源项目,旨在打造高性能、去中心化且低成本的区块链平台2。它采用独特的 Proof of History(PoH)共识机制,结合 Tower BFT 等技术,实现了每秒数千笔交易的高吞吐量,确认时间仅 400 毫秒,每笔交易中位数费用为 0.00064 SOL1…

windows将ollama及模型安装到D盘或其他盘符

Ollama官网 在ollama安装包存放的文件路径输入 cmd 回车自动打开命令窗口 在命令行输入一下内容: 软件包名称 /DIR=指定目录OllamaSetup.exe /DIR=E:\software\Ollama 弹出安装程序,直接Install ---------------------------------------------------------------------…

【DIY】通达信DIY添加扫雷宝、地图和复盘(1)

【写在前面】炒股软件有很多,电脑版我用的多的通达信,手机版用的多的是同花顺。之前还有一些其它的软件,比如A股通、短线王、wind股票、雪球等都有自己的特色。自己不喜欢一次装好多软件,所以就产生了聚合的念头。通达信通赢版可以登录多券商账号,懒得来回切换,所以选择以…

RabbitMQ进阶--TTL,死信队列,持久化,磁盘监控

一.消息的TTL机制 RabbitMQ中的TTL(Time-To-Live,存活时间)是指消息或队列的过期时间。TTL机制允许你指定消息在未被消费前可以存活的时间长度,超过这个时间后,消息将被视为“死亡”,并从队列中移除。这种机制对于控制消息的有效性和资源管理非常有用。 防止积压消息:当…

day41 基础的模块

基础模块 ad-hoc命令模式pingcommandshellcopyfilescriptcrongroupuser1.先看资料,看模块的语法 - 官网,看实例用法,语法注解 - ansible-doc -s 模块名 - 你自己的学习笔记2.每一个模块独有的参数语法,都是 ad-hoc命令模式ansible 主机组 -m 模块名 -a "模块独有的…

Java面试基础

一、Java基础语法 1.1 环境搭建与开发 1、JDK、JRE、JVM的关系和区别?(高频plus)JDK:Java Development Kit,Java开发工具包,包含了JRE和一系列Java开发工具。 JRE:Java Runtime Environment,Java程序的运行环境,包含JVM、Java核心类库等。JRE只能用来运行Java应用程序…

day41 Ansible剧本的语法

day41--ansible剧本的学习 1.剧本的主题的内容剧本就是两大部分的知识点 -ansible 模块(以及模块的参数) -yaml语法3.工作以后 维护公司的剧本 -看剧本,看懂了,维护,修改 -修改各种的参数,文件的路径,文件的名字,服务的端口名字(就是该ansible各种的参数,参数的值)2。…

Windows系统迁移用户目录

Windows系统随着使用时间的延长,系统盘空间占用会越来越大,对于一些硬盘空间有限的主机而言,C盘空间剩余就越来越少,稍不注意可以可能就被占满了,导致系统都无法启动。正好我自己的笔记本电脑也遇到了同样的问题,因此就想把C:\Users目录迁移到其他分区中。 在实践中,使用…

iphone---苹果手机传输文件

使用手机,拍了一些照片和剪辑了一些视频,但是总感觉将手机里面的图片及视频传到电脑里面和麻烦,今天试了一下用U盘传,效率还可以,但是其中也遇到了一些问题,就在这里记录下: 传输方法:插入U盘,选择图片或文件,点击【分享】选择【存储到“文件”】点击【左上角浏览】…

Python+硅基流动API实现小说转有声读物

一、注册硅基流动账号获取文本转语音api 1.注册登录硅基流动注册、登录硅基流动查看apikey查看赠送的免费额度点击文档中心2.查看文本转语音api查看文本转语音api查看api使用指南查看文本转语音api的python调用方法二、python实现文本转语音功能 1.python调用api实现文本转语音…

蓝绿部署深度解析:从理论到实践的全方位学习指南

一、蓝绿部署的基本概念 (一)定义 蓝绿部署是一种软件发布策略,通过设置两个相同的环境(“蓝色”和“绿色”),最大限度地减少停机并降低与部署新版本应用程序相关的风险。 (二)工作原理 蓝绿部署的核心思想是将应用程序的部署过程分为两个阶段:测试阶段和生产阶段。在…

Elasticsearch 高级

Elasticsearch 高级建议阅读顺序:Elasticsearch 入门 Elasticsearch 搜索 Elasticsearch 搜索高级 Elasticsearch高级(本文)1. nested 类型 1.1 介绍 Elasticsearch 中的 nested 类型允许你在文档内存储复杂的数据结构,比如一个用户可能有多个地址,或者一个博客文章可能有…