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年秋的題目難度會和這套卷子平齊,甚至最后一題可能設得更難,讓我們拭目以待吧(滑稽)。 然后就被打臉了,跑路嘍~