2021年 PAT(乙级)
7-1 好数 (15 分)
题目描述
好数是指由一对正整数 a<b 按照 a 2 + a b + b 2 a ^2+ab+b ^2 a2+ab+b2这个规则生成的数,a 和 b 就称为这个好数的源头。例如 91 就是一个好数,因为 5 2 + 5 × 6 + 6 2 = 91 5^2+5×6+6 ^2=91 52+5×6+62=91,于是数对(5,6)就是 91 的源头。而对于一个好数,其源头并不一定唯一,例如(1,9)就是 91 的另一个源头。
本题就要求你编写程序,判断一个给定的数字是否好,并且输出好数的所有源头。
输入格式:
输入在第一行给出一个不超过 100 的正整数 N,随后 N 行,每行给出一个不超过
1
0
4
10^4
104的正整数。
输出格式:
对于每一个输入的数字,如果其是好数,则首先在一行中输出 Yes
,然后每行输出它的一个源头,格式为 a
b
,按 a
的递增顺序输出;否则在一行中输出 No
和比该数大的最小的好数,其间以空格分隔,然后每行输出这个好数的一个源头,格式同上。
输入样例:
3
1
91
50
输出样例:
No 7
1 2
Yes
1 9
5 6
No 52
2 6
代码
利用常规方法(通过式子判断)模拟
#include <iostream>
#include <cstring>
#include <map>
using namespace std;
const int N = 1e4;
int n , k = 0;
pair<int , int> p[N];//利用pair存储
bool is_haoshu(int u)
{
int f = 0;
for(int i = 1 ; i * i < u ; ++i) {
for(int j = i + 1 ; j * j < u ; ++j) {
if(i * i + i * j + j * j == u) {
f = 1;
p[k].first = i;
p[k].second = j;
++k;
}
}
}
if(f) {
return true;
} else {
return false;
}
}
void print()
{
for(int i = 0 ; i < k ; ++i) {
cout << p[i].first << " " << p[i].second << endl;
}
//初始化
memset(p , 0 , sizeof p);
k = 0;
}
int main()
{
cin >> n;
while(n--) {
int num;
cin >> num;
if(is_haoshu(num)) {
cout << "Yes\n";
print();
} else {
cout << "No ";
for(int i = num + 1 ; ; ++i) {
if(is_haoshu(i)) {
cout << i << endl;
print();
break;
}
}
}
}
return 0;
}
利用哈希表存储所有数的源头
#include<iostream>
#include<vector>
#include<unordered_map>
#include<set>
using namespace std;
typedef pair<int, int> PII;
int main()
{
unordered_map<int, vector<PII>> hash;
set<int> S;
//存储
for (int i = 1; i <= 100; i++) {
for (int j = i + 1; j <= 100; j++) {
int num = i * i + i * j + j * j;
S.insert(num);
hash[num].push_back({ i, j });
}
}
int n;
cin >> n;
for (int i = 0; i < n; i++) {
int question;
cin >> question;
if (!S.count(question)) {//如果不存在
question = *S.upper_bound(question);
//*为解引用
cout << "No" << ' ' << question << endl;
}else {
cout << "Yes" << endl;
}
for (auto& p : hash[question]) {
cout << p.first << ' ' << p.second << endl;
}
}
return 0;
}
解题思路
- 首先观察数据范围每个N都在 1 0 4 10^4 104以内,大致可以算一下对于最大的N假设它的源头是(i,j)均在60左右 所以用一个哈希表记录每个N可行的源头
unordered_map<int, vector<PII>>
,这样在处理每一个询问时可以直接查表输出N的所有源头同时因为预处理时是按照从小到大的顺序的,所有哈希表李的每个源头都是按题目要求排列好的。
- 若询问未在预处理的好数里,要返回离他最近的一个好数并输出其结果,可以用一个C++里的有序集合
set
,使用二分函数upper_bound
可以快速找到set中比询问大的第一个数。
7-2 数以类聚 (20 分)
题目描述
我们把所有各位数字的乘积相同的数归为一类。例如 1362 和 2332 就是同一类,因为 1×3×6×2=2×3×3×2。给定 N 个正整数,请你判断它们可以被归成多少不同的类?
输入格式:
输入在第一行给出一个正整数 N
(
≤
1
0
5
)
(≤10 ^5 )
(≤105),第二行给出 N 个不超过
1
0
7
10 ^7
107的正整数,数字间以空格分隔。
输出格式:
在一行中输出 N 个给定的整数可以归类的数量、以及规模最大的类中最小的乘积。数字间以一个空格分隔。
输入样例:
10
1234 98 329 9552 47621 8862 5539 2333 5365 463
输出样例:
7 54
样例说明:
10 个给定数字对应的各位数字乘积依次为:24、72、54、450、336、768、675、54、450、72。所以一共有 7 个不同的种类,即:24、72、54、450、336、768、675。
其中规模最大的有 2 个 54、2 个 72、2 个 450。即这 3 个种类都分别对应了 2 个数字,在规模相等、都是最大的情况下,我们输出最小的乘积 54。
代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
multiset <int> s;//从小到大存储其乘积数
set<int> s0;//去重,计算归类数量
multimap<int , int , greater<int>> m;//从大到小存储次数(键)和乘积(值)
while(n--) {
int num;
cin >> num;
int t = 1;
while(num) {
t *= num % 10;
num /= 10;
}
s.insert(t);
s0.insert(t);
}
for(auto it = s.begin() ; it != s.end() ; ++it) {
m.insert(make_pair(s.count(*it) ,*it));
}
cout << s0.size() << " " << m.begin()->second;
return 0;
}
思路
- 利用排序容器将值进行排序(set乘积排序 , map 次数排序)
- 先利用
multiset
容器插入乘积 - 在利用
mmultimap
容器插入乘积的个数(set中s.count
函数)和乘积 - 再利用
set
容器去重计算归类乘积数
7-3 自定义判题程序 (20 分)
题目描述
在每次允许插入、删除、修改一个字符的前提下,用最少的动作把一个字符串变成另一个字符串,是一道著名的可以用动态规划解决的问题。但判题的麻烦之处在于,虽然最小代价是唯一的,但变换方法却是不唯一的。例如把 PAT
变成 PTA
最少需要 2 步,可以保持第 1 个字母不变,修改后面 2 个字母,也可以保持第 1、2 个字母不变,在 A
前面插入 T
,后面删除 T
。由于拼题 A 系统的默认判题程序只能通过比对输出文件来判断对错,对这种正确答案输出不唯一的题目就不能处理了,需要出题者额外编写一个自定义判题程序来解决问题。
本题就请你编写这个自定义判题程序,读入两个字符串和用户程序产生的输出结果,判断他们的程序输出是否正确。
输入格式:
输入首先在前两行分别给出两个不超过 1000 个字符、以回车结束的非空字符串,第 1 行对应初始字符串,第 2 行对应目标字符串。
随后一行给出一个正整数 N
(≤100),为需要判断的提交数。
接下来是 N
个提交的输出结果,每个结果占 2 行:第 1 行给出一个整数 K
(不超出 32 位 int 的范围),为用户输出的动作数;第 2 行顺次描述对初始字符串的每个字符所做的操作:
如果这个字符不变,则在对应位置输出 0
如果这个字符被删除,则在对应位置输出 1
如果这个字符被改变,则在对应位置输出 2
如果这个字符前面或者后面插入了一个字符,则在插入的位置输出 3
注意我们要求用户提交的行首尾和数字间均无空格,所以如果有多余空格应判为错误。
题目保证这个操作序列不为空。
输出格式:
对每个正确的提交,在一行中输出 AC
;否则输出 WA
。
注意:这里并不要求你会用动态规划求出最优解。所以对“正确提交”的判断,并不以动态规划求出的最优解为根据! 对于用户输出的 K,如果其操作序列的确给出了 K 步操作并可以完成字符串的变换,则称为一个“可行解”。所谓“正确提交”,是指所有提交的可行解中的最优解。
输入样例:
This is a test.
Tom is a cat.
6
8
02330001100022100
8
11113330000001113300
6
022100000012200
5
033310000000200
6
0 2 2 1 000000 1 2 2 00
6
012200000022100
输出样例:
WA
WA
AC
WA
WA
AC
代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
string s1 , s2 ,s0;
getline(cin , s1);
getline(cin , s2);
int t ,k[11000] , maxx = 0x3f3f3f3f;
cin >> t;
for(int kk = 0 ; kk < t ; ++kk) {
int n;
cin >> n;
getchar();
string c;
getline(cin , s0);
int cnt = 0 , j = 0 , f = 0;
for(int i = 0 ; i < s0.size() ; ++i) {
if(s0[i] == '0') {
c += s1[j];
++j;
} else if(s0[i] == '1') {
++cnt;//删除无需处理,自动加1
++j;
} else if(s0[i] == '2') {
c += '#';
++cnt;
++j;
} else if(s0[i] == '3') {
c += "#";
++cnt;
} else { //包括空格
f = 1;
}
}
if(c.size() != s2.size() || cnt != n) {
f = 1;
}
for(int i = 0 ; i < s2.size() ; ++i) {
if(c[i] == s2[i] || c[i] == '#') {
continue;
} else {
f = 1;
}
}
if(!f) {
k[kk] = n;
maxx = min(maxx , n);//获得最小步数
} else {
k[kk] = -1;
}
}
for(int i = 0 ; i < t ; ++i) {
if(k[i] == maxx) {
cout << "AC\n";
} else {
cout << "WA\n";
}
}
return 0;
}
思路
- 输入字符串的长度只能等于目标字符串的长度,并且其1,2,3操作的总和数等于输入的动作数
- 输入的字
- 符串只能含有0,1,2,3
- 在分析操作时,重新定义一个空字符串,对这个空字符串进行操作
7-4 数组与链表 (20 分)
题目描述
让我们来设计这样一种数组与链表结合的整数存储的结构 A:
这种结构首先初始化一个长度为
L
0
L_0
L0的整型数组
A
0
A_0
A0,返回给用户使用。当用户访问第 i 个元素 A[i] 的时候,如果
0
≤
i
<
L
0
0 ≤ i < L_0
0≤i<L0,则 A[i] 对应
A
0
A_0
A0[i],系统就返回
h
0
h_0
h0 + i × sizeof(int) 作为要访问的地址,其中
h
0
h_0
h0是数组
A
0
A_0
A0的起始位置,sizeof(int) 是数组元素的大小,这里元素就是 int 型,占 4 个字节。
当用户访问越界时(即
i
≥
L
0
i ≥ L_0
i≥L0),系统将另外开辟一个长度为
L
1
L_1
L1的数组
A
1
A_1
A1。此时 A[i] 对应
A
1
A_1
A1[j](这里 i 和 j 之间的映射关系留给你去推导)。如果
0
≤
j
<
L
1
0≤j<L_1
0≤j<L1,则返回
h
1
+
j
×
s
i
z
e
o
f
(
i
n
t
)
h_1+j × sizeof(int)
h1+j×sizeof(int) 作为要访问的地址,其中
h
1
h_1
h1是数组
A
1
A_1
A1的起始位置。当
A
1
[
j
]
A_1[j]
A1[j] 继续越界时,系统就按照上述规则继续开辟另一个长度为
L
2
的
数
组
A
2
L_2 的数组 A_2
L2的数组A2,并以此类推。
本题就请你实现这个系统功能,为用户的任何一次访问返回对应元素的地址。
输入格式:
输入第一行给出两个正整数 N
(
≤
1
0
4
)
(≤ 10 ^ 4)
(≤104)和 K$(≤
1
0
3
)
10 ^ 3)
103),分别为最多允许开出的数组个数、以及用户访问的次数。
此后 N 行,每行给出两个正整数,分别是一个数组的初始地址( ≤ 1 0 7 ≤10 ^ 7 ≤107 )和长度(≤ 100),其间以空格分隔。题目保证这些数组占据的空间无重叠。
最后一行顺序给出 K 个用户访问的数组下标,为区间 [ 0 , 2 20 ] [0, 2 ^ {20}] [0,220] 内的整数。
输出格式:
对每个用户访问,在一行中输出对应的地址。但如果用户的访问超出了 N 个数组的存储范围,则这个访问是非法的,要输出 Illegal Access
,并且对这个访问不采取任何处理。
最后一行请输出上述过程中一共创建了多少个数组。
输入样例:
6 7
2048 5
128 6
4016 10
1024 7
3072 12
9332 10
2 12 25 50 28 8 39
输出样例:
2056
4020
1040
Illegal Access
3072
140
3116
5
代码
#include <iostream>
using namespace std;
const int N = 1e4;
struct array {
int h0 , l , l0 = 0;
}a[N];
int main()
{
int n , k , l_sum = 0;
cin >> n >> k;
for(int i = 0 ; i < n ; ++i) {
cin >> a[i].h0 >> a[i].l;
a[i].l0 += a[i].l + a[i - 1].l0;
l_sum += a[i].l;
}
int Max = 1;//emmmm最小的就是1
while(k--) {
int num;
cin >> num;
if(num > l_sum - 1) {
cout << "Illegal Access\n";
continue;
}
for(int i = 0 ; i < n ; ++i) {
if(num >= a[i - 1].l0 && num < a[i].l0) {
cout << a[i].h0 + (num - a[i - 1].l0) * sizeof(int) << endl;
Max = max(Max , i + 1);
break;
}
}
}
cout << Max<< endl;
return 0;
}
7-5 取帽子 (25 分)
题目描述
拼题er们觉得戴帽子会令自己看上去很帅,所以他们不管到哪里都会戴着帽子。有一天他们去到一家餐厅,服务员把他们的帽子收集了堆起来保管。当大家要离开的时候,发现帽子被像上图那样摞起来了。于是你的任务就是帮他们排好队,使得每个人都能按顺序顺利取到自己的帽子。
已知每顶帽子的大小都不相同,并且帽子的尺寸跟帽子主人的体重有关 —— 越重的人戴的帽子就越大。
输入格式:
输入第一行给出一个正整数 N
(
≤
1
0
4
(≤10^4
(≤104),为拼题er的人数。随后一行给出 N 个不同的帽子尺寸,为不超过
1
0
5
10 ^5
105的正整数,顺序是从帽子堆的底部向上给出。最后一行给出 N 个不同的体重,顺序对应编号从 1 到 N 的拼题er。体重是不超过
1
0
6
10^6
106的正整数。一行中的数字以空格分隔。
输出格式:
在一行中按照取帽子的顺序输出帽子主人的编号。数字间以 1 个空格分隔,行首尾不得有多余空格。
输入样例:
10
12 19 13 11 15 18 17 14 16 20
67 90 180 98 87 105 76 88 150 124
输出样例:
3 4 8 6 10 2 1 5 9 7
样例说明:
第一顶帽子的尺寸是最大的 20,所以对应第 3 个人的最大体重 180,于是第 3 个人排在最前面。
第二顶帽子的尺寸是第 6 小的 16,对应第 6 小的体重 98,是第 4 个人,于是第 4 个人下一个走。
以此类推。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4;
int a[N] ,b[N] , c[N];
int main()
{
int n;
cin >> n;
map<int , int> m1;
for(int i = 1 ; i <= n ; ++i) {
cin >> a[i];
c[i] = a[i];
}
sort(c + 1 , c + n + 1);
for(int i = 1 ; i <= n ; ++i) {
m1[c[i]] = i;
}
map<int , int> m2;
int j = 0;
for(int i = 1 ; i <= n ; ++i) {
cin >> b[i];
m2[b[i]]
}
sort(b + 1 , b + n + 1);
for(int i = n ; i >= 1 ; --i) {
cout << m2[b[m1[a[i]]]] << " \n"[i == 1];
}
return 0;
}