L题
cpp
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
// 定义一个邻接表 G 来表示图
// G[i] 存储与节点 i 相邻的所有边的信息
// 每个边信息用 pair<int, int> 表示,第一个元素是相邻节点的编号,第二个元素是该边的唯一编号
vector<pair<int, int>> G[MAXN];
bool vis[MAXN];
// 布尔类型数组 vis,用于标记每条边是否已经被访问过
// 初始值默认为 false,当边被访问后设为 true
int n, b[MAXN], tot;
// n 表示输入的三角形层数,由用户输入决定
// b 数组用于辅助计算每个节点的编号
// tot 是一个计数器,用于给每条边分配唯一的编号
vector<int> ans;
// 存储最终的一笔画顶点序列,即欧拉路径
// 向图中添加无向边的函数
// u 和 v 分别表示边的两个端点
void add_edge(int u, int v)
{
// 在节点 u 的邻接表中添加一条指向节点 v 的边
// 边的编号为 tot,同时使用 emplace_back 直接在原地构造元素,提高效率
G[u].emplace_back(v, tot);
// 在节点 v 的邻接表中添加一条指向节点 u 的边
// 由于是无向边,所以边的编号与上面添加的边相同
// 添加完边后,将 tot 的值加 1,为下一条边分配新的编号
G[v].emplace_back(u, tot++);
}
// 深度优先搜索函数,用于寻找欧拉路径
// x 表示当前正在访问的节点
void dfs(int x)
{
// 遍历与节点 x 相邻的所有边
for (auto &i: G[x])
{
// 如果该边已经被访问过,则跳过这条边,继续检查下一条边
if (vis[i.second]) continue;
// 标记这条边为已访问
vis[i.second] = true;
// 递归调用 dfs 函数,从相邻节点 i.first 继续进行深度优先搜索
dfs(i.first);
}
// 当从节点 x 出发的所有边都被访问完后
// 将节点 x 添加到结果序列 ans 中,这是回溯过程
ans.push_back(x);
}
int main()
{
// 从标准输入读取一个整数 n,表示三角形的层数
scanf("%d", &n);
// 初始化 b 数组的第一个元素为 1
// b 数组用于计算每个节点的编号,后续会根据规律更新
b[0] = 1;
// 计算 b 数组的值
// b[i] 表示第 i 层第一个节点的编号
// 第 i 层的第一个节点编号等于第 i - 1 层第一个节点编号加上 i
for (int i = 1; i <= n; ++i)
{
b[i] = b[i - 1] + i;
}
// 构建图的边
// 对于每一层的每个三角形,连接其三个顶点形成边
for (int i = 0; i < n; ++i)
{
for (int j = 0; j <= i; ++j)
{
// 计算当前三角形的三个顶点编号
// p1 是第 i 层第 j 个节点的编号
// p2 是第 i + 1 层第 j 个节点的编号
// p3 是第 i + 1 层第 j + 1 个节点的编号
int p1 = b[i] + j, p2 = b[i + 1] + j, p3 = b[i + 1] + j + 1;
// 添加边 (p1, p2)
add_edge(p1, p2);
// 添加边 (p2, p3)
add_edge(p2, p3);
// 添加边 (p3, p1)
add_edge(p3, p1);
}
}
// 从节点 1 开始进行深度优先搜索
// 因为本题构建的图一定存在欧拉回路,所以可以从任意节点开始搜索,这里选择节点 1
dfs(1);
// 输出结果标志,表示存在一笔画方案
printf("Yes\n");
// 输出最终的一笔画顶点序列
for (int i = 0; i < ans.size(); ++i)
{
// 输出节点编号
printf("%d", ans[i]);
// 如果是最后一个节点,输出换行符;否则输出空格
printf("%c", " \n"[i + 1 == ans.size()]);
}
return 0;
}
C题
cpp
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
string a[MAXN];
// 定义一个字符串数组 a,用于存储输入的所有单词
int n, m;
// n 表示单词的数目,m 表示查询的数目
// 计算两个字符串的最长公共前缀长度
int lcp(const string &A, const string &B)
{
int i = 0;
// 当 i 小于两个字符串的长度且对应位置字符相等时,i 自增
while (i < A.size() && i < B.size() && A[i] == B[i]) ++i;
return i;
// 返回最长公共前缀的长度
}
int main(int argc, char *argv[])
{
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
// 从标准输入读取单词的数目 n 和查询的数目 m
for (int i = 0; i < n; ++i)
{
cin >> a[i];
}
sort(a, a + n);
// 对存储的单词按字典序进行排序,这样相邻单词的公共前缀更有可能较长,便于后续计算
int mx = 0, sum = 0;
// mx 用于记录最长单词的长度,sum 用于记录总的按键次数
for (int i = 0; i < n; ++i)
{
sum += (int) a[i].size() * 2;
// 先假设每次输入一个新单词都需要完整输入该单词,再完整删除该单词,所以乘以 2
if (i) sum -= lcp(a[i], a[i - 1]) * 2;
// 如果不是第一个单词,计算当前单词和前一个单词的最长公共前缀长度
// 因为公共前缀部分不需要重复输入和删除,所以减去公共前缀长度的两倍
mx = max(mx, (int) a[i].size());
// 更新最长单词的长度
}
cout << sum - mx << endl;
// 最终结果为总的按键次数减去最长单词的长度
// 因为最后一个单词输入后不需要删除,所以减去最长单词的长度得到最少按键次数
return 0;
}
E题
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
using namespace std;
// 定义一个极大值 INF,用于二分查找的右边界
const int INF = 1000000001;
// 计算在时间 t 内发生的碰撞对数
long long count_pairs(long long t, vector<long long> &u, vector<long long> &v)
{
long long result = 0;
// p1 和 p2 是两个指针,用于遍历 v 数组
int p1 = 0, p2 = 0;
// 遍历所有向右运动的小球的初始位置
for (auto &i: u)
{
// 移动 p2 指针,使其指向第一个大于等于 i 的位置
while (p2 < v.size() && v[p2] < i) ++p2;
// 移动 p1 指针,使其指向第一个大于 i + t 的位置
while (p1 < v.size() && v[p1] <= i + t) ++p1;
// 计算在时间 t 内,当前向右运动的小球与向左运动的小球的碰撞对数
result += p1 - p2;
}
return result;
}
int main()
{
int n;
long long k;
// 读取小球的数量 n 和要查询的碰撞对数 k
scanf("%d %lld", &n, &k);
// 存储每个小球的初始位置和速度
vector<pair<long long, long long>> a(n);
// 存储向右运动的小球的初始位置
vector<long long> u;
// 存储向左运动的小球的初始位置
vector<long long> v;
for (int i = 0; i < n; ++i)
{
long long x, y;
// 读取每个小球的初始位置 x 和速度 y
scanf("%lld %lld", &x, &y);
if (y == 1)
{
// 如果速度为 1,表示向右运动,将其初始位置加入 u 数组
u.push_back(x);
}
else
{
// 如果速度为 -1,表示向左运动,将其初始位置加入 v 数组
v.push_back(x);
}
}
// 对向右运动的小球的初始位置进行排序
sort(u.begin(), u.end());
// 对向左运动的小球的初始位置进行排序
sort(v.begin(), v.end());
// 二分查找的左边界
int l = 0;
// 二分查找的右边界
int r = INF;
// 记录满足条件的最小时间
int ans = 0;
while (l <= r)
{
// 计算中间时间
int mid = (l + r) / 2;
// 计算在时间 mid 内发生的碰撞对数
auto nowk = count_pairs(mid, u, v);
if (nowk >= k)
{
// 如果碰撞对数大于等于 k,说明时间可能过长,缩小右边界
r = mid - 1;
}
else
{
// 如果碰撞对数小于 k,说明时间过短,更新 ans 并扩大左边界
ans = mid;
l = mid + 1;
}
}
if (ans == INF)
{
// 如果 ans 仍然等于 INF,说明无法达到第 k 对碰撞,输出 No
printf("No\n");
return 0;
}
ans++;
// 输出 Yes,并格式化输出达到第 k 对碰撞的时间
printf("Yes\n%d.%c00000\n", ans / 2, "05"[ans & 1]);
return 0;
}