127. 我素故我在
题目
问题描述
有这样一种素数叫纯素数(YY出来的名字),当它是一个多位数的时候,你把它的末位去掉之后余下的数依然是一个素数。比如说2393,2393 本身是一个素数,它的末位去掉之后,余下的是239。239 是一个素数,它的末位去掉之后,余下的是23 。23是一个素数,它的末位去掉之后,余下的是2 。2依然还是一个素数。纯素数的长度叫做"维"。2393 是一个4维素数。3797也是一个4维素数。
输入说明
第一行先给出一共有多少组数据N(N<=1000),接下来有N组数据.
每组包括一个整数T(1<=T<=8)。
输出说明
按照从小到大的顺序输出所有的T维纯素数。
个人总结
思路
- 利用递归构造:由于纯素数去掉末位仍是素数,说明n位纯素数一定是由n-1位纯素数在末尾添加一位数字构成的。这是一种树状结构。
- 搜索策略:使用DFS(深度优先搜索)从高位向低位构造。从1位素数(2, 3, 5, 7)出发,每次在末位拼接数字并判定新数是否为素数。如果不构成素数,则不再向下搜索(剪枝)。
- 预处理方案:由于N高达1000且T范围固定,必须在主循环外进行一次性预处理,将1到8维的所有纯素数分类存入缓存容器(如 vector 数组)。
- 结果整理:搜索完成后,对每一维度的结果进行升序排序,确保输出符合从小到大的要求。
易错点
- 搜索方式的选择(关键):预处理也分效率。若通过遍历 1 到 10^8 的所有数字并判断其是否为纯素数,即使只做一次且后续剪枝,判定总量也会达到亿级,导致 TLE。而通过 DFS 仅对素数分支进行构造,判定的数字量极小(仅数百个),效率有天壤之别。
- 素数判定等号:is_prime 函数中 i * i <= n 的等号不可省略。若写成 i * i < n,会导致 4, 9, 25, 49 等完全平方数被判定为素数。这不仅导致结果错误,还可能因为错误的判定让 DFS 构造出大量非素数的分支,增加计算开销。
- 判定函数特判:数字 1 既非素数也非合数。在 DFS 起始或判定函数中,必须对 1 进行拦截,否则会导致逻辑错误。
- 类型溢出:处理多位素数时,构造过程(n * 10 + i)虽在 int 范围内,但使用 long long 是考试中避免溢出的稳妥习惯。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
vector<vector<ll>> res(9);
bool is_prime(ll n) {
if (n == 1) return false;
for (ll i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
void dfs(ll n, int len) {
// 不是素数或超过层数,直接返回结束
if (!is_prime(n) || len > 8) return;
// 是素数的处理
res[len].push_back(n);
for (ll i = 1; i < 10; i++) {
dfs(n * 10 + i, len + 1);
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
// 寻找所有纯素数
for (ll i = 1; i < 10; i++) {
dfs(i,1);
}
// 排序
for (ll i = 1; i <= 8; i++) {
sort(res[i].begin(), res[i].end());
}
// 输出
int N;
int T;
cin >> N;
while (N--) {
cin >> T;
for (auto n : res[T]) {
cout << n << "\n";
}
}
return 0;
}
128. 汉诺塔问题的第m步
题目
问题描述
给定三根杆A、B、C和大小不同的几个盘子。这些盘子按尺寸递减顺序套在A杆上,最小的在最上面。现在的任务是把这些盘子从A杆移到C杆且保持原来堆放顺序。在实现任务时,每次只能移动一个盘子,且任何时刻不允许大的盘子放在小的盘子上面,B杆可以作为辅助存放杆。求:总共有n个圆盘时,搬动过程中的第m步是从哪个杆到哪个杆。
输入说明
你的程序需要从标准输入设备(通常为键盘)中读入多组测试数据。每组输入数据由一行组成,每行输入一个整数表示盘子数n,1≤n≤10,以及步数m,两个数据之间以一个空格分隔。行首和行尾没有多余的空格,两组数据之间也没有多余的空行。
输出说明
对每组测试数据,你的程序需要向标准输出设备(通常为启动该程序的终端)依次输出一行对应的答案,该行中输出第m步移动的情况,如第m步是从A移到B,则输出"A--B"(不包括引号)。如果移动过程不存在第m步,则输出"none" (不包括引号)。
两组数据之间无空行,第一组前及最后一组后也无空行。
个人总结
思路
- 采用经典汉诺塔的递归分治模型。移动n个盘子的过程本质上只有三步:先把上面n-1个盘子从源杆移到辅助杆,接着把最底下的第n个盘子从源杆移到目标杆,最后把那n-1个盘子从辅助杆移到目标杆。
- 设置一个全局并且不断递减的步数倒计时变量。递归过程中只要遇到单盘子的真实移动,步数就减一。
- 给递归函数增加布尔类型的返回值以实现剪枝。当倒计时归零时,打印当前的出发杆和到达杆,同时返回真值。各层递归一接到真值就立刻向外终止并传递,阻止后续多余的计算。
- 运用映射数组将整型下标与字符ABC对应起来,简化字符输出逻辑。如果整个递归过程结束依然返回假值,说明指定步数超出了将n个圆盘全部移走所需的总步数上限,直接输出none。
易错点
- 递归调用时源杆、辅助杆、目标杆的参数顺序极易写错。转移前n-1个盘子时,真正的目标是将其放入暂存区,因此原目标杆扮演起了辅助杆的角色;把暂存区的n-1个盘子拿回时,原源杆变空了,扮演起了辅助的作用。
- 容易漏掉两次递归调用中间那一步真实的单盘移动操作。中间那步代表转移最底下最大的盘子,它算进了总移动步数内,必须在此刻让步数减一并判断是否归零。
优化思路
这题其实可以不用模拟,直接用数学办法来解决,下面有请 grok 同志来介绍一下并给出数学版的代码:
这题是经典汉诺塔问题,但不需要输出全部移动序列,只要求第 m 步具体是从哪根杆移到哪根杆(n ≤ 10,总步数最多 1023,效率不是问题)。
核心思路利用汉诺塔的递归结构直接定位第 m 步,而不用模拟所有移动:
把 n 个盘子从 src 移到 dst(用 tmp 辅助)分为三段:
前 2^(n-1)-1 步:把 n-1 个盘子从 src 移到 tmp(用 dst 辅助)
第 2^(n-1) 步:把最大盘子从 src 直接移到 dst
后 2^(n-1)-1 步:把 n-1 个盘子从 tmp 移到 dst(用 src 辅助)
给定 m,先算总步数 total = 2^n - 1。如果 m < 1 或 m > total 输出 "none"。否则根据 m 落在哪一段,递归进入对应子问题(中间一步直接输出杆名)。
这样每组数据只需 O(n) 时间,递归深度最多 10,完全符合考场要求。
下面是完整 C++ 代码(已加注释,变量命名简单直接,符合上机习惯):
cpp
#include <bits/stdc++.h>
using namespace std;
// 递归查找第 step 步是从哪移到哪(假设 step 在当前子问题合法范围内)
string getMove(int n, char src, char dst, char tmp, long long step) {
if (n == 1) { // 边界:只有一个盘子,直接移动
return string(1, src) + "--" + string(1, dst);
}
long long sub = (1LL << (n - 1)) - 1; // n-1 个盘子的步数
if (step <= sub) {
// 第 m 步在前半部分,递归处理 n-1 移到辅助杆
return getMove(n - 1, src, tmp, dst, step);
} else if (step == sub + 1) {
// 正好是移动最大盘子的那一步
return string(1, src) + "--" + string(1, dst);
} else {
// 第 m 步在后半部分,递归处理 n-1 从辅助杆移到目标杆,步数要减掉前面
return getMove(n - 1, tmp, dst, src, step - sub - 1);
}
}
int main() {
int n;
long long m;
while (cin >> n >> m) { // 多组输入直到 EOF
long long total = (1LL << n) - 1;
if (m < 1 || m > total) {
cout << "none" << endl;
} else {
string ans = getMove(n, 'A', 'C', 'B', m);
cout << ans << endl;
}
}
return 0;
}
碎碎念
以下是一些毫无营养、只会浪费观看者时间的碎碎念:
这题把我写美了。写完看着自己的代码小巧思越看越觉得妙啊,虽然俺不知道数学规律,俺也没啥代码题常识,但是俺怎么看都觉得自己想出来的模拟法太优雅了。实在忍不了了,必须得找个地方抒发一下情感。
整个代码的精髓就是函数的返回值是 bool ,有种大道至简之美。这里返回的 bool 在很多地方都派上了用场。甚至用来判断作为中途停止的控制流在这里都只是常规操作,最妙的是main中那次调用本身的true和false已经代表了m是否存在结果!函数不做任何数学运算,只负责向上层报告"找到了"或"没找到",下层如果没找到,就让上层继续找,回溯到main最初调用的那次还没找到那就是真没有,输出 none。所以 main函数变得非常简单,完全不需要写任何单独判断,一次调用如果是true就不用管,因为该输出的已经输出了,如果是false就再输出一个none就结束。
另一点我觉得很妙的是,一开始我想的是让函数返回需要输出的那一步步骤,让 main 输出,但是那样有点复杂,而且就不能返回布尔值了。后来我突然想到,这是个算法题啊!如果这是个正常项目我肯定得想办法把结果返回去存起来,可是这里一个输入就一个输出,还没复杂的格式要求,我完全可以找到结果时直接当场输出,然后就把结果扔掉不管了,提交上去机器只看你输出了什么,根本没人知道我压根就没保存也没处理答案,一下子省了很多麻烦。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
char i2c[] = {'A','B','C'};
int step;
bool move(int src, int help, int tgt, int n) {
// 将 src 的 n 个盘子 移到 tgt 上
// help 是辅助杆
// 返回 true 代表已经找到目标,不用再找了
// 返回 false 是没找到
// 只挪一个盘子,挪完这步就结束了
if (n == 1) {
step--;
// 找到目标步骤
if (step == 0) {
cout << i2c[src] << "--" << i2c[tgt] << "\n";
return true;
}
return false;
}
// 要挪不止一个盘子
//先把上面 n-1 个挪到辅助杆
if (move(src,tgt,help, n-1)) return true;
// 再把最下面的挪到目标杆上
step--;
if (step == 0) {
cout << i2c[src] << "--" << i2c[tgt] << "\n";
return true;
}
// 最后把上面 n-1 个从辅助杆挪到目标杆
if (move(help,src,tgt,n-1)) return true;
return false;
}
int main() {
int n, m;
while (cin >> n >> m) {
step = m;
if (!move(0,1,2,n)) {
cout << "none\n";
}
}
return 0;
}
129. 数字游戏
题目
问题描述
现在,有许多给小孩子玩的数字游戏,这些游戏玩起来简单,但要创造一个就不是那么容易的了。 在这,我们将介绍一种有趣的游戏。
你将会得到N个正整数,你可以将一个整数接在另一个整数之后以制造一个更大的整数。 例如,这有4个数字123, 124, 56, 90,他们可以制造下列整数─ 1231245690, 1241235690, 5612312490, 9012312456, 9056124123....等,总共可以组合出24(4!)种数字。 但是,9056124123是最大的那一个。
你可能会想这是个简单的事情,但对刚有数字概念小孩来说,这会是个简单的任务吗?
输入说明
输入含有多组测试数据。
每组测试资料两行,第一行为一个正整数N(N<= 50),第二行将有N 个正整数。
当N=0代表输入结束。
输出说明
对每一组测试数据,输出一行,输出利用这N个整数可结合成的最大整数。
个人总结
思路
这道题的核心思路是把每个正整数当作字符串处理,避免数值溢出问题。代码先用while循环持续读取N,当N等于0时直接返回结束所有测试用例。
每组数据把N个数字存入vector<string> v中。接着单独定义cmp函数,里面直接返回a+b大于b+a的结果,作为sort的比较规则。sort结束后,整个vector里的字符串顺序就是拼接最优顺序。
最后用范围for循环依次输出每个字符串,连成完整最大整数,再输出换行结束本组。
这个自定义排序(用 a + b > b + a 作为比较规则)能直接得到全局最优解,核心原因在于它把"局部最优决策"变成了"全局一致的顺序",不需要枚举 N! 种排列。
简单说:最优拼接结果的字符串一定是"字典序最大"的 。
而 a + b > b + a 精确地判断了"a 放在 b 前面"还是"b 放在 a 前面"会让局部(甚至整体)更大。
为什么排序一次就够了?因为最优序列有一个关键性质:
- 在最优答案里,任意相邻两个数字 都必须满足"前者 + 后者 > 后者 + 前者"。
如果违反(假设相邻的 a 在 b 后面,但 a+b < b+a),那就把这相邻两个交换一下:- 前面的前缀完全一样
- 后面的后缀完全一样
- 只有中间这两位拼接变大了
→ 整个数字立刻变大,矛盾于"最优"。
所以最优排列不能有任何相邻逆序。
易错点
这题最大的易错点在于试图自己搞各种小巧思,试图找规律自己排序解这个题。
建议老老实实记住最大数拼接就是 a + b < b + a。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
bool cmp(const string &a, const string &b) {
return a + b > b + a;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int N;
while (cin >> N) {
if (N == 0) return 0;
vector<string> v(N);
for (int i = 0; i < N; i++) cin >> v[i];
sort(v.begin(),v.end(),cmp);
for (auto &n : v) {
cout << n;
}
cout << "\n";
}
return 0;
}
计算机英语
翻译练习
Convolutional neural networks (CNNs) are among the most important models in deep learning and are widely used in computer vision tasks such as image classification, object detection, and image segmentation . Compared with traditionalfully connected neural networks, CNNs significantly reduce the number of parameters by using local connections and weight sharing , which lowers computational complexity. Convolutional layers can automatically extract features from raw images, including edges, textures, and shapes. As the number of layers increases, the model is able to learn more abstract and complex feature representations. In addition, pooling layers are usually used to reduce the spatial dimensions of feature maps, thereby decreasing computational cost and improving robustness. In practical applications, CNNs have achieved remarkable success in areas such as medical image analysis, autonomous driving, and face recognition. However, training deep convolutional neural networks often requires large amounts of labeled data and powerful computing resources, which remains an important challenge in current research.
卷积神经网络(CNN)是深度学习中最重要的模型之一,广泛应用于图像分类、目标检测和图像分割等计算机视觉任务。
与传统的全连接神经网络相比,CNN 通过局部连接和权重共享大幅减少了参数数量,从而降低了计算复杂性。
卷积层可以从原始图像中自动提取特征,包括边缘、纹理和形状。随着层数的增加,模型能够学习到更加抽象和复杂的特征表示。
此外,通常使用池化层来减小特征图的空间维度,从而降低计算成本并提高鲁棒性。
在实际应用中,CNN 在医学图像分析、自动驾驶和人脸识别等领域取得了巨大的成功。
然而,训练深度卷积神经网络通常需要大量的标注数据和强大的计算资源,这仍然是当前研究中的一个重要挑战。
背单词打卡
