2024 保研夏令营机试刷题记录
2024年夏令营机试中,nova分享了她的经验和做题记录,她主要刷了2023的推免题和2019的上机题。
在处理题目时,她注重题目的做题量,保持速度,以适应不同难度题目。以下是一些她具体的题目和解决方式:
-
2023 推免题:
- 全在其中:此题采用双指针法,通过简单遍历判断一个字符串是否为另外一个字符串的子列。
- 最接近的分数:解决此题,采用暴力扫描分子和分母并结合乘法防止除法精度的问题。
- 踩方格:用 DFS 和 DP 方法分别尝试不同解法,通过回溯来计算所有不同的路径。
- 核电站:使用了动态规划来解决不同的放置核物质方案数目的计算问题。
-
2019 上机题:
- 寻找特殊年号:通过存储特殊年号并使用二分查找快速确定答案。
- 图片处理:直接通过遍历和平均相邻像素来模糊处理图片。
- 括号生成:使用 DFS 方法生成所有合法括号组合,并按字典序输出。
- 二叉树形态种类:通过卡特兰数来计算不同结点的二叉树形态数量。
- 酒鬼问题:此题通过动态规划来计算最优的选择方案。
- 最短路径:采用经典图算法,如 Dijkstra 解决有向图最短路径问题。
她在总结中提到,这一系列题目并不十分困难,可以通过多次练习提升熟练度和速度。此外,她还建议针对经典算法进行专门的练习,以确保在各种考试中保持良好的表现。
刷 leetcode 还是没什么意思,准备一天来一套真题(笑,不做特别难的题和特别麻烦的大模拟,确保做题速度。
会按照一个顺序选择刷题。
2023 研究生推免 (5/14)
全在其中
A:全在其中
总时间限制: 1000ms 内存限制: 65536kB 描述 你设计了一个新的加密技术,可以用一种聪明的方式在一个字符串的字符间插入随机的字符串从而对信息进行编码。由于专利问题,我们将不会详细讨论如何在原有信息中产生和插入字符串。不过,为了验证你的方法,有必要写一个程序来验证原来的信息是否全在最后的字符串之中。
给定两个字符串s和t,你需要判断s是否是t的“子列”。也就是说,如果你去掉t中的某些字符,剩下字符将连接而成为s。
输入 输入包括多个测试样例。每一个都是由空格分隔的由字母数字ASCII字符组成的两个特定的字符串s和t。s和t的长度不超过100000。
输出 对于每个测试样例,如果s是t的“子列”,则输出”Yes”,否则输出”No”
签到题。双指针扫一遍即可。
#include <iostream>
#include <string>
using namespace std;
int n;
bool isSub(string str, string sub) {
int i = 0, l = 0;
while (i < sub.size() && l < str.size()) {
if (str[l] == sub[i]) i++, l++;
else l++;
}
return i == sub.size();
}
int main() {
string substr, str;
while (cin >> substr >> str) {
cout << (isSub(str, substr) ? "Yes" : "No" ) << endl;
}
}
最接近的分数
B:最接近的分数 总时间限制: 1000ms 内存限制: 65536kB 描述
分母不超过 N 且 小于 A/B 的最大最简分数是多少?
输入
三个正整数N,A,B,相邻两个数之间用单个空格隔开。。
输出
两个正整数,分别是所求分数的分子和分母,中间用单个空格隔开。
从 1~N 扫一遍,选择分子和分母。因为 ,所以 可以接受。
注意就是用乘法防止除法精度问题。最大 1000*1000,爆不了 int
#include <iostream>
using namespace std;
int n, a, b;
int main() {
cin >> n >> a >> b;
// 分母小于等于 n,小于 a / b
int mxi = -1, mxj = -1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
// j/i
if (j*b < a *i) {
if (mxi == -1 || mxj == -1 || j * mxi > mxj * i) mxj = j, mxi = i;
}
}
}
cout << mxj << " " << mxi << endl;
}
踩方格
踩方格
总时间限制: 1000ms 内存限制: 65536kB 描述
有一个方格矩阵,矩阵边界在无穷远处。我们做如下假设: a. 每走一步时,只能从当前方格移动一格,走到某个相邻的方格上; b. 走过的格子立即塌陷无法再走第二次; c. 只能向北、东、西三个方向走;
请问:如果允许在方格矩阵上走n步,共有多少种不同的方案。2种走法只要有一步不一样,即被认为是不同的方案。
输入 允许在方格上行走的步数n(n \le 20)
输出 计算出的方案数量
一眼 DP 或者 dfs,用 DFS 写的回溯,最多运行 20 * 3 次。这里开了个 41 * 21 的数组,然后从 20 开始走,数组用于判断走没走过。
#include <iostream>
using namespace std;
int mat[42][21];
int res;
int n;
int dx[3] = {0, 1, 0};
int dy[3] = {-1, 0, 1};
void dfs(int x, int y, int u) {
if (u == n) {
res ++;
return;
}
for (int i = 0; i < 3; i++) {
int xx = x + dx[i];
int yy = y + dy[i];
if (mat[xx][yy]) continue;
mat[xx][yy] = 1;
dfs(xx, yy, u + 1);
mat[xx][yy] = 0;
}
}
int main() {
cin >> n;
mat[20][0] = 1;
dfs(20, 0, 0);
cout << res;
}
核电站
D:核电站
总时间限制: 5000ms 单个测试点时间限制: 1000ms 内存限制: 131072kB 描述 一个核电站有 N 个放核物质的坑,坑排列在一条直线上。如果连续 M 个坑中放入核物质,则会发生爆炸,于是,在某些坑中可能不放核物质。
任务:对于给定的 N 和 M ,求不发生爆炸的放置核物质的方案总数。
输入 只有一行,两个正整数 N,M (1 < N < 50, )。
输出 一个正整数 S,表示方案总数。
2 \le M \le 5最卡的一题。一开始也用了一个 DFS,每个位置摆和不摆两种。写完发现是 的复杂度,寄!不过如果真遇到应该可以骗几个测例的分。
选择 DP,一开始写了一个二维 DP,但是一直没有想清楚转移方程。
代表到 i 位置合法的个数,j 是二维的,代表第 i 位置放或者不放。对于 i < m 的情况,直接加就完事了;对于 i == m 的情况,减掉第一个位置放的一种情况;对于 i > m 的情况,这里一直没想清楚,后面突然理解了,我们要去掉的情况是明朗的:i-m+1 ~ i 共 m - 1 个位置都放,i-m 这个位置不放,而对于前面 i-m-1 个位置,我们怎么放都可以,因此不合法的个数是 1 * f[i-m-1] 个。
然后就是要注意爆 int 的问题,假设 m 不存在,那么我们就有 2^n 个方案,就 算有 m 这个限制条件,也不会减特别多,所以用 long long 最保险。
/* O(2^n) */
void dfs(int x, int u, int p) {
// u 当前已放置的个数
// x 当前放置的位置
// p 当前连续放置多少个
if (p >= m) return;
if (x == n) {
res ++;
return;
}
dfs(x + 1, u, 0); // 不在这里放
dfs(x + 1, u + 1, p + 1); // 在这里放
return;
}
long long f[55];
int main() {
cin >> n >> m;
// f[i][0/1] 代表在 i 位置放和不放的总数情况 (合法的)
// f[n][0] + f[n][1]
// f[i][0] = f[i-1][0] + f[i-1][1]
// f[i][1] = f[i-1][0] + f[i-1][1] - 不合法方案
// 对于 i == m 的情况,我们仅需减去一种方案,即全放的方案
// 对于 i > m 的情况,我们需要减去的是这样一种方案:i-m+1~i 共 m - 1 个位置都放,i-m 没有放。但是 i - m - 1 前的方案是不确定的,因此乘法原理处理。
f[0][0] = 1;
for (int i = 1; i < m; i++) f[i][1] = f[i][0] = f[i-1][0] + f[i-1][1];
for (int i = m; i <= n;) {
f[i][0] = f[i-1][0] + f[i-1][1];
f[i][1] = f[i-1][0] + f[i-1][1] - 1;
break;
}
for (int i = m + 1; i <= n; i++) {
f[i][0] = f[i-1][0] + f[i-1][1];
f[i][1] = f[i-1][0] + f[i-1][1] - f[i-1-m][1] - f[i-1-m][0];
}
cout << f[n][0] + f[n][1];
}
之后,可以发现其实我们用一维合并一下更好。
long long f[55];
int main() {
cin >> n >> m;
// 可以发现我们能够压缩一维
// f[i][1] + f[i][0] = 2*(f[i-1][0] + f[i-1][1]) - 不合法方案
f[0] = 1;
for (int i = 1; i <= n; i++) {
f[i] = 2 * f[i-1];
if (i == m) f[i] -= 1;
if (i > m) f[i] -= f[i-1-m];
}
cout << f[n];
}
Highways
G:Highways 总时间限制: 1000ms 内存限制: 65536kB 描述 The island nation of Flatopia is perfectly flat. Unfortunately, Flatopia has no public highways. So the traffic is difficult in Flatopia. The Flatopian government is aware of this problem. They're planning to build some highways so that it will be possible to drive between any pair of towns without leaving the highway system.
Flatopian towns are numbered from 1 to N. Each highway connects exactly two towns. All highways follow straight lines. All highways can be used in both directions. Highways can freely cross each other, but a driver can only switch between highways at a town that is located at the end of both highways.
The Flatopian government wants to minimize the length of the longest highway to be built. However, they want to guarantee that every town is highway-reachable from every other town.
输入 The first line of input is an integer T, which tells how many test cases followed. The first line of each case is an integer N (), which is the number of villages. Then come N lines, the i-th of which contains N integers, and the j-th of these N integers is the distance (the distance should be an integer within [1, 65536]) between village i and village j. There is an empty line after each test case.
输出 For each test case, you should output a line contains an integer, which is the length of the longest road to be built such that all the villages are connected, and this value is minimum.
概括一下题意,就是说要建高速,从 1~n 个点之间两两建,输出可以修建的方案里最长高速最小的距离。其实翻译一下就是找最小生成树。这里也有贪心性质,可以证明下。假设现在有一颗最小生成树,它的边权值最大为 x,假设存在另一颗生成树,其边权值最大为 y,且满足 x > y,由于两两连通,那么一定通过这条权值边生成一个更小的生成树,矛盾,因此最小生成树的边权值最大即为题目所要求的。
理解之后就是 Prim 或者 KRUSKAL 的板子题了。注意读入的时候,它到自身的边权值为 0,改成无穷大就好。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
int n;
void prim(vector<vector<int>>& mat) {
vector<int> dist(mat.size(), 0x3f3f3f3f);
vector<bool> st(mat.size(), false);
dist[1] = 0;
int res = INT_MIN;
for (int i = 1; i < mat.size(); i++) {
int v = -1;
for (int j = 1; j < mat.size(); j++) if (!st[j] && (v == -1 || dist[j] < dist[v] )) v = j;
st[v] = true;
res = max(res, dist[v]);
for (int j = 1; j < mat.size(); j++) {
if (!st[j] && dist[j] > mat[v][j]) dist[j] = mat[v][j];
}
}
cout << res << endl;
}
int main() {
scanf("%d", &n);
while (n--) {
string tmp;
getline(cin, tmp);
int m;
scanf("%d", &m);
vector<vector<int>> mat(m + 1, vector<int>(m + 1));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= m; j ++) {
scanf("%d", &mat[i][j]);
if (i == j) mat[i][j] = 0x3f3f3f3f;
}
}
prim(mat);
}
}
2023 研究生上机 (5/15)
寻找特殊年号
A:寻找特殊年号
查看提交统计提问
总时间限制: 1000ms 内存限制: 65535kB
描述
年号中的每个数之和为20的年号是特殊年号。例如:2099、1991、1892是特殊的年号,而2021则不是。给定一个年号,找出严格大于给定年号的最小特殊年号。
输入
年号:整数y(1000≤y≤9000)。
输出
特殊年号:严格意义上大于y的最小年号,并且它的每个数之和为20。
不知道有没有更聪明的做法,一开始是直接每个往上找,感觉会 TLE,就首先存了特殊的,然后二分去找第一个大于当前的。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> spe;
int getSum(int i) {
int sum = 0;
while (i) {
sum += i % 10;
i /= 10;
}
return sum;
}
void gen() {
for (int i = 1000; i < 10000; i++) {
if (getSum(i) == 20) spe.push_back(i);
}
sort(spe.begin(), spe.end());
}
int main() {
gen();
int n;
while (cin >> n) {
auto nxt = upper_bound(spe.begin(), spe.end(), n);
cout << *nxt << endl;
}
}
图像模糊处理
B:图像模糊处理
查看提交统计提问
总时间限制: 1000ms 内存限制: 65536kB
描述
给定n行m列的图像各像素点的灰度值,要求用如下方法对其进行模糊化处理:
- 四周最外侧的像素点灰度值不变;
- 中间各像素点新灰度值为该像素点及其上下左右相邻四个像素点原灰度值的平均(舍入到最接近的整数)。
输入
第一行包含两个整数n和m,表示图像包含像素点的行数和列数。1 ≤ n ≤ 100,1 ≤ m ≤ 100。
接下来n行,每行m个整数,表示图像的每个像素点灰度。相邻两个整数之间用单个空格隔开,每个元素均在0~255之间。
输出
n行,每行m个整数,为模糊处理后的图像。相邻两个整数之间用单个空格隔开。
没看懂,直接做就完了?
#include <iostream>
#include <vector>
using namespace std;
int n, m;
int mat[110][110];
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%d", &mat[i][j]);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i == 1 || i == n || j == 1 || j == m) {
cout << mat[i][j];
if (i == n && j == m) continue;
if (j == m) cout << endl;
else cout << " ";
continue;
}
int sum = mat[i-1][j] + mat[i][j] + mat[i+1][j] + mat[i][j-1] + mat[i][j+1];
cout << sum / 5 << " ";
}
}
}
括号生成
C:括号生成
查看提交统计提问
总时间限制: 1000ms 内存限制: 65536kB
描述
Paul是一名数学专业的同学,在课余选修了C++编程课,现在他能够自己写程序判断判断一个给定的由'('和')'组成的字符串是否是正确匹配的。可是他不满足于此,想反其道而行之,设计一个程序,能够生成所有合法的括号组合,请你帮助他解决这个问题。
输入
输入只有一行N,代表生成括号的对数(1 ≤ N ≤ 10) 。
输出
输出所有可能的并且有效的括号组合,按照字典序进行排列,每个组合占一行。
dfs,让左括号数量大于右括号即可。
#include <iostream>
#include <vector>
using namespace std;
int n;
vector<char> res;
void dfs(int l, int r, int u) {
if (l > n || r > n || r > l) return;
if (u == 2*n) {
for (auto i : res) {
cout << i;
}
cout << endl;
return;
}
res.push_back('(');
dfs(l + 1, r, u + 1);
res.pop_back();
res.push_back(')');
dfs(l, r + 1, u + 1);
res.pop_back();
}
int main() {
cin >> n;
dfs(0, 0, 0);
}
有多少种二叉树
D:有多少种二叉树
查看提交统计提问