浙江大学计算机与软件学院2021年考研复试上机
7-1 Square Friends (20 分)
时间限制:400 ms 内存限制:64 MB
For any given positive integer n, two positive integers A and B are called Square Friends if by attaching 3 digits to every one of the n consecutive numbers starting from A, we can obtain the squares of the n consecutive numbers starting from B.
For example, given n=3, A=73 and B=272 are Square Friends since 73984=2722, 74529=2732, and 75076=2742.
Now you are asked to find, for any given n, all the Square Friends within the range where A≤MaxA.
Input Specification:
Each input file contains one test case. Each case gives 2 positive integers: n (≤100) and MaxA (≤106), as specified in the problem description.
Output Specification:
Output all the Square Friends within the range where A≤MaxA. Each pair occupies a line in the format A B
. If the solution is not unique, print in the non-decreasing order of A; and if there is still a tie, print in the increasing order of B with the same A. Print No Solution.
if there is no solution.
Sample Input 1:
3 85
Sample Output 1:
73 272
78 281
82 288
85 293
Sample Input 2:
4 100
Sample Output 2:
No Solution.
解析:
题目大意:对于n,A,B,使得B2去掉末三位数字等于A,(B+1)2去掉末三位数字等于A+1,以此类推,(B+n-1)2去掉末三位数字等于A+n-1,现在给定n和A的上限MaxA,求所有满足条件的A与B,按照A升序(第一判断标准)且B升序(第二判断标准)输出。如果无解,输出No Solution.
。
双重循环,外层遍历A,范围为[1, MaxA],内层遍历B,范围为[A*1000再开方, (A+1)*1000再开方]。对每对A与B,检查n个连续的数字是否满足要求。
#include <cmath>
#include <iostream>
using namespace std;
int n;
bool check (int A, int B) {
for (int i = 0; i < n; i++) {
if (B * B / 1000 != A) return false;
A++;
B++;
}
return true;
}
void test () {
int MaxA, i, j, flag = 0;
scanf("%d %d", &n, &MaxA);
for (i = 1; i <= MaxA; i++) {
int B1 = (int)sqrt(i * 1000);
int B2 = (int)sqrt((i+1) * 1000);
for (j = B1; j <= B2; j++) {
if (check(i, j)) {
flag = 1; // 有输出了
printf("%d %d\n", i, j);
};
}
}
if (flag == 0) {
printf("No Solution.");
return;
}
}
int main () {
test();
return 0;
}
7-2 One Way In, Two Ways Out (25 分)
时间限制:400 ms 内存限制:64 MB
Consider a special queue which is a linear structure that allows insertions at one end, yet deletions at both ends. Your job is to check, for a given insertion sequence, if a deletion sequence is possible. For example, if we insert 1, 2, 3, 4, and 5 in order, then it is possible to obtain 1, 3, 2, 5, and 4 as an output, but impossible to obtain 5, 1, 3, 2, and 4.
Input Specification:
Each input file contains one test case. For each case, the first line gives 2 positive integers N and K (≤10), which are the number of insertions and the number of queries, respectively. Then N distinct numbers are given in the next line, as the insertion sequence. Finally K lines follow, each contains N inserted numbers as the deletion sequence to be checked.
All the numbers in a line are separated by spaces.
Output Specification:
For each deletion sequence, print in a line yes
if it is indeed possible to be obtained, or no
otherwise.
Sample Input:
5 4
10 2 3 4 5
10 3 2 5 4
5 10 3 2 4
2 3 10 4 5
3 5 10 4 2
Sample Output:
yes
no
yes
yes
解析:
题目大意:一个队列,只能从一端插入数据,但可以从两端删除数据。现在给定一段数据互不相同的插入序列,然后给一些删除序列,判断这些删除顺序的可行性。
用两个指针pi与pj分别指向插入序列insertion与删除序列deletion。pi初始指向0,表示第一个数据待插入。每读到一个待删除数据时,有以下情况:
1、当前待删除数据等于待插入数据,把数据进队列然后马上出队列就可以了(相当于什么也不做),然后pi右移,表示下一个数据待插入;
2、当前待删除数据不等于待插入数据,且待删除数据还没有插入,那就一口气把待删除数据之前未插入的数据都插入;
3、当前待删除数据不等于待插入数据,且待删除数据已经插入了,那就检查队列两端是不是待删除数据,不是就意味着这个数无法删除,该删除序列无法实现。
数据结构上,可以用deque,它的用法类似于vector,但是支持时间复杂度为O(1)的两端插入删除操作。
#include <deque>
#include <unordered_set>
#include <iostream>
using namespace std;
void test () {
int N, K;
int i, j;
scanf("%d %d", &N, &K);
deque<int> insertion(N);
for (i = 0; i < N; i++) scanf("%d", &insertion[i]);
for (int k = 0; k < K; k++) {
unordered_set<int> inserted;
deque<int> deletion(N), q;
for (j = 0; j < N; j++) scanf("%d", &deletion[j]);
for (j = 0, i = 0; j < N; j++) {
if (deletion[j] == insertion[i]) {
// 数据进队列然后马上出队列
inserted.insert(insertion[i++]);
}
else {
if (!inserted.count(deletion[j])) {
// 一口气把i所指,到待删除数据之前的都插入
while (i < N && insertion[i] != deletion[j]) {
q.push_back(insertion[i]);
inserted.insert(insertion[i++]);
}
i++;
} else {
// 检查该数据能否删除
if (q.empty()) break;
else if (q.back() == deletion[j]) q.pop_back();
else if (q.front() == deletion[j]) q.pop_front();
else break;
}
}
}
if (j == N) printf("yes\n");
else printf("no\n");
}
}
int main () {
test();
return 0;
}
7-3 Preorder Traversal (25 分)
时间限制:1200 ms 内存限制:64 MB
Suppose that all the keys in a binary tree are distinct positive integers. Given the postorder and inorder traversal sequences, you are supposed to output the last number of the preorder traversal sequence of the corresponding binary tree.
Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N (≤ 50,000), the total number of nodes in the binary tree. The second line gives the postorder sequence and the third line gives the inorder sequence. All the numbers in a line are separated by a space.
Output Specification:
For each test case, print in one line the last number of the preorder traversal sequence of the corresponding binary tree.
Sample Input:
7
1 2 3 4 5 6 7
2 1 4 3 7 5 6
Sample Output:
5
解析:
题目大意:给出二叉树的中序和后序遍历,求前序遍历的最后一个结点。
用套路的建树再中序遍历是可以做的,时间给得很充足。不建树也可以做,由于前序遍历的顺序是根左右,那么寻找前序遍历最末项的方法为:
1、先看根结点有没有右子树,有就往右子树找;
2、如果没有右子树,则看有没有左子树,有就往左子树找;
3、如果左子树也没有,那么这个根结点就是结果。
而根据中序和后序就能得到每个结点的左子树和右子树。
#include <vector>
#include <iostream>
using namespace std;
void test () {
int N, i;
scanf("%d", &N);
vector<int> in(N), post(N);
for (i = 0; i < N; i++) scanf("%d", &post[i]);
for (i = 0; i < N; i++) scanf("%d", &in[i]);
int in1 = 0, in2 = N - 1, post1 = 0, post2 = N - 1;
while (in1 < in2) {
int root = post[post2];
i = in1;
// 在中序中找到根的序号
while (in[i] != root) i++;
if (i < in2) { // 有右子树,往右子树找
in1 = i + 1;
post1 = post2 - (in2 - i);
post2--;
} else if (i > in1) { // 有左子树,往左子树找
in2 = i - 1;
post2 = post1 + (i - in1) - 1;
} else { // 叶子结点,即为结果
printf("%d", root);
return;
}
}
printf("%d", in[in1]);
}
int main () {
test();
return 0;
}
7-4 Load Balancing (30 分)
时间限制:600 ms 内存限制:64 MB
Load balancing (负载均衡) refers to efficiently distributing incoming network traffic across a group of backend servers. A load balancing algorithm distributes loads in a specific way.
If we can estimate the maximum incoming traffic load, here is an algorithm that works according to the following rule:
- The incoming traffic load of size S will first be partitioned into two parts, and each part may be again partitioned into two parts, and so on.
- Only one partition is made at a time.
- At any time, the size of the smallest load must be strictly greater than half of the size of the largest load.
- All the sizes are positive integers.
- This partition process goes on until it is impossible to make any further partition.
For example, if S=7, then we can break it into 3+4 first, then continue as 4=2+2. The process stops at requiring three servers, holding loads 3, 2, and 2.
Your job is to decide the maximum number of backend servers required by this algorithm. Since such kind of partitions may not be unique, find the best solution -- that is, the difference between the largest and the smallest sizes is minimized.
Input Specification:
Each input file contains one test case, which gives a positive integer S (2≤N≤200), the size of the incoming traffic load.
Output Specification:
For each case, print two numbers in a line, namely, M, the maximum number of backend servers required, and D, the minimum of the difference between the largest and the smallest sizes in a partition with M servers. The numbers in a line must be separated by one space, and there must be no extra space at the beginning or the end of the line.
Sample Input:
22
Sample Output:
4 1
Hint:
There are more than one way to partition the load. For example:
22
= 8 + 14
= 8 + 7 + 7
= 4 + 4 + 7 + 7
or
22
= 10 + 12
= 10 + 6 + 6
= 4 + 6 + 6 + 6
or
22
= 10 + 12
= 10 + 6 + 6
= 5 + 5 + 6 + 6
All requires 4 servers. The last partition has the smallest difference 6−5=1, hence 1 is printed out.
解析:
题目大意:有一根木棒,不断把它掰断形成新的木棒,每次只取其中一根掰成两部分,所有的木棒长度都是正整数,并且这些木棒中最短者长度的两倍始终大于最长的,问最多可以掰成多少根木棒(第一判断标准),并且最长木棒与最短木棒的长度之差最小(第二判断标准)。
要用DFS进行搜索,维护一个木棒长度列表以及这中间的最大值和最小值。最暴力的做法是,每次取出某一根木棒,试探着掰成两部分再加入长度列表,更新最大值和最小值并看看是否满足条件,如此反复直到不能掰断为止。当然,这样可能会超时。
在某个掰断的过程中,设选取的木棒长度为L,新掰成的两根木棒长度分别为L1和L2,且L1 ≤ L2,实际上,为了满足题目条件,只需要取出最长的木棒掰开,并且L1一定满足⌊L/3⌋+1 ≤ L1 ≤ ⌊L/2⌋,L1一定是所有木棒中最短的。
L1 ≤ ⌊L/2⌋很容易理解,下面先证明每次只需要取最长的木棒掰开:
证明:假设长度列表中最大值为max,最小值为min,由题意有min×2>max。假如取的不是最长的木棒,所取木棒最大长度为Lm=max-1,那么掰开后较短的部分最长也只有⌊(max-1)/2⌋<min,违反了题设条件。所以只能取最长的木棒。
接下来证明L1 ≥ ⌊L/3⌋+1:
证明:1、假设L能被3整除,设L/3=k,k为正整数。假如将木棒掰成L1=k和L2=2k,就违反了条件。随着L1减少,L2会增大,条件依然不成立。而掰成L1=k+1与L2=2k-1,2(k+1)>2k-1恒成立,同样L1增大会使得L2减小,条件依然满足。
2、假设L不能被3整除,先考虑L ≥ 4的情况,设⌊L/3⌋=k,k为正整数。假如将木棒掰成L1=k和L2=L-k,由3k<L得2k<L-k,违反了条件。随着L1减少,L2会增大,条件依然不成立。而掰成L1=k+1与L2=L-k-1,由k+1>L/3得2(k+1)>L-k-1恒成立,同样L1增大会使得L2减小,条件依然满足。再考虑L=1,2的情况,为1时不能再掰,为2时只能掰成1+1,也满足1 ≥ ⌊2/3⌋+1。
综上,L1 ≥ ⌊L/3⌋+1成立。
最后证明L1是所有木棒中最短的:
证明:假设长度列表中最大值为max,最小值为min,由题意有min×2>max。取出max掰开,所得L1最长为⌊max/2⌋,有⌊max/2⌋ ≤ max/2<min,又L1 ≤ L2,故L1为最短。
这样就可以写出较为优化的代码了:用一个lengths数组存放各个长度木棒的数量,搜索时维护三个参数:最大长度,最小长度,木棒数量。每次搜索时,取出最长的木棒,令对应的数量减一,如果对应数量变成了0,就顺序找到新的最大长度。然后把木棒掰成L1和L2,用循环遍历各种情形,加入lengths数组,并确定下一层DFS的最大长度和最小长度。
注:姥姥在设置时间限制时还是手下留了很多情的,考试的时候,即使你想不到这么多,也不一定拿不了满分。例如第二份代码,只考虑到了取出最长木棒,数据结构用的相对比较繁琐的map,但是照样能拿满分。
#include <iostream>
using namespace std;
int N;
int max_servers = 0, min_diff = 0x3fffffff; // 服务器最大数量,最小极差
int lengths[202];
// 最短木棒,最长木棒,木棒总数
void DFS (int min_l, int max_l, int count) {
if (count > max_servers) {
max_servers = count;
min_diff = max_l - min_l;
} else if (count == max_servers && max_l - min_l < min_diff) {
min_diff = max_l - min_l;
}
// max2是去掉一根最长木棒后的最长木棒
int i, max2 = max_l;
lengths[max_l]--;
if (lengths[max_l] == 0) {
// 顺序找次小的
for (max2 = max_l - 1; max2 >= 2; max2--) {
if (lengths[max2] > 0) break;
}
}
for (i = max_l / 2; i >= max_l / 3 + 1; i--) {
if (i * 2 <= max2) break;
int j = max_l - i;
lengths[i]++;
lengths[j]++;
DFS(i, max(j, max2), count + 1);
lengths[i]--;
lengths[j]--;
}
lengths[max_l]++;
}
void test () {
scanf("%d", &N);
lengths[N] = 1;
DFS(N, N, 1);
printf("%d %d\n", max_servers, min_diff);
}
int main () {
test();
return 0;
}
第二份代码:map自带排序,借此很容易得到最长木棒和最短木棒。
#include <map>
#include <iostream>
using namespace std;
int N;
int max_servers = 0, min_diff = 0x3fffffff;
map<int, int> m;
void DFS (int min_l, int max_l, int count) {
if (count > max_servers) {
max_servers = count;
min_diff = max_l - min_l;
} else if (count == max_servers && max_l - min_l < min_diff) {
min_diff = max_l - min_l;
}
int i, j;
// 取出最长木棒
int key = m.rbegin()->first;
m[key]--;
if (m[key] == 0) m.erase(key);
// 更新最长和最短木棒
max_l = m.rbegin()->first;
min_l = m.begin()->first;
int max1 = max_l, min1 = min_l;
for (i = key / 2; i >= 1; i--) {
j = key - i;
max_l = max(max_l, j);
min_l = min(min_l, i);
if (min_l * 2 > max_l) {
m[i]++; m[j]++;
DFS(min_l, max_l, count + 1);
m[i]--; m[j]--;
if (m[i] == 0) m.erase(i);
if (m[j] == 0) m.erase(j);
} else break;
// 恢复max_l和min_l
max_l = max1;
min_l = min1;
}
m[key]++;
}
void test () {
int i, j;
scanf("%d", &N);
if (N == 3) {
printf("1 0");
return;
}
for (i = N / 2; i >= 1; i--) {
j = N - i;
if (i * 2 <= j) break;
m.clear();
m[i]++; m[j]++;
DFS(i, j, 2);
}
printf("%d %d\n", max_servers, min_diff);
}
int main () {
test();
return 0;
}
总结
编号 | 标题 | 分数 | 类型 |
---|---|---|---|
7-1 | Square Friends | 20 | 5.1 简单数学 |
7-2 | One Way In, Two Ways Out | 25 | 4.6 two pointers |
7-3 | Preorder Traversal | 25 | 9.2 二叉树的遍历 |
7-4 | Load Balancing | 30 | 8.1 DFS |
总地来说,作为2021年计院机试恢复的产物,这套卷子有一定难度。第一道题毫不例外地稍微来了点下马威,这题目反正我读了好几遍才读懂。这两年的PAT甲级第一题总是或多或少有点门槛,最有名的大概就是熊猫PP奶了吧。第二、三题都比较常规,第二题相当于逻辑题,第三题套路题。绝大部分难度应该还是集中在第四题上,它实际上是道数学题,用推导所得数学关系进行剪枝,所幸姥姥手下留情了。预计21年秋的题目难度会和这套卷子平齐,甚至最后一题可能设得更难,让我们拭目以待吧(滑稽)。 然后就被打脸了,跑路喽~