PAT甲级题分类汇编——理论
本文为PAT甲级分类汇编系列文章。
理论这一类,是让我觉得特别尴尬的题,纯粹是为了考数据结构而考数据结构。看那Author一栏清一色的某老师,就知道教数据结构的老师的思路就是和别人不一样。
题号 | 标题 | 分数 | 大意 | Author |
1051 | Pop Sequence | 25 | 判断一个序列是否是pop序列 | CHEN, Yue |
1052 | Linked List Sorting | 25 | 链表排序 | CHEN, Yue |
1057 | Stack | 30 | 一个有中位数功能的stack | CHEN, Yue |
1074 | Reversing Linked List | 25 | 分段逆转链表 | CHEN, Yue |
1089 | Insert or Merge | 25 | 判断插入排序或归并排序 | CHEN, Yue |
1097 | Deduplication on a Linked List | 25 | 链表去重 | CHEN, Yue |
1098 | Insertion or Heap Sort | 25 | 判断插入排序或堆排序 | CHEN, Yue |
好几道题在上MOOC的时候就在数据结构题集里面做过,有1051、1074和1089。还有1098,是之前做merge那道的时候想一并做的,但后来因为merge花了太多时间就跳过了。
这次讲1074、1098、1051和1057 4道题。没错,不按题号顺序。
1074:
这可能是我做的第一道PAT题,也可能是第一道卡住的题。其实,所有用5位整数来模拟链表的题,好像都必须创建100000大小的数组,直接寻址才能跑进时间限制, std::map 是不行的。不过呢,如果我一开始是用C做的,肯定就直接建数组了,也就没有后面的问题了。
这道题的讲解在数据结构课程里有。
因为是早期作品,所以代码长得比较尴尬,懒得改了,我想睡觉。
#include <iostream>
#include <vector>
#include <utility>
#include <algorithm>
#include <type_traits>
#include <stack>
#include <iomanip> struct Node
{
int address;
int data;
int next;
}; int main(int argc, char const *argv[])
{
int first, n, k;
std::cin >> first >> n >> k;
std::vector<Node> data(n);
for (int i = ; i != n; ++i)
std::cin >> data[i].address >> data[i].data >> data[i].next; decltype(data) list;
int address = first;
for (int i = ; i != n; ++i)
{
auto pos = std::find_if(std::begin(data), std::end(data), [address](const Node& node) {
return node.address == address;
});
list.emplace_back(*pos);
address = pos->next;
if (address == -)
break;
}
n = list.size(); auto begin = list.begin();
auto end = begin + (list.end() - begin) / k * k;
for (; begin != end; begin += k)
{
std::stack<Node> stack;
for (auto iter = begin; iter != begin + k; ++iter)
stack.push(*iter);
for (auto iter = begin; iter != begin + k; ++iter)
{
*iter = stack.top();
stack.pop();
}
}
for (int i = ; i != n - ; ++i)
list[i].next = list[i + ].address;
list[n - ].next = -; std::cout << std::setfill('');
for (auto& node : list)
{
std::cout << std::setw() << node.address << ' ';
std::cout << std::setw() << node.data << ' ';
if (node.next == -)
{
std::cout << "-1";
break;
}
std::cout << std::setw() << node.next << std::endl;
} return ;
}
1098:
它的兄弟题目1089在数据结构课程中讲过,包括这道题中也用到的判断插入排序的方法。
#include <iostream>
#include <vector>
#include <algorithm> int main()
{
int size;
std::cin >> size;
std::vector<int> original(size);
std::vector<int> partial(size);
for (auto& i : original)
std::cin >> i;
for (auto& i : partial)
std::cin >> i; int cur = ;
for (; cur != size && partial[cur] >= partial[cur - ]; ++cur)
;
bool insertion = true;
for (auto i = cur; i != size; ++i)
if (partial[i] != original[i])
{
insertion = false;
break;
} if (insertion)
{
std::cout << "Insertion Sort" << std::endl;
int insert = partial[cur];
for (--cur; cur >= ; --cur)
if (partial[cur] > insert)
partial[cur + ] = partial[cur];
else
break;
partial[cur + ] = insert;
}
else
{
std::cout << "Heap Sort" << std::endl;
int cur = size - ;
auto top = partial[];
for (; cur >= ; --cur)
if (partial[cur] <= top)
break;
if (cur >= )
{
std::pop_heap(partial.begin(), partial.begin() + cur + );
partial[cur] = top;
}
}
auto end = partial.end() - ;
for (auto iter = partial.begin(); iter != end; ++iter)
std::cout << *iter << ' ';
std::cout << *end;
}
其实这道还要稍微简单一点,因为那道题要自己写merge,这道题的heap可以用标准库。<algorithm> 提供了 std::pop_heap 等函数用于堆操作。至于查看堆顶元素,把起始迭代器解引用就好,标准库没有给。
BTW,建堆的操作是O(N)的,证明起来挺组合数学的。
还有呢,我感觉这两道题贼尴尬。
1051:
判断一个序列是不是一个stack中pop出来的序列。
#include <iostream>
#include <vector>
#include <stack> class Stack : public std::stack<int>
{
using super = std::stack<int>;
public:
explicit Stack(int _cap)
: capacity_(_cap)
{
;
}
void push(const int& _data)
{
if (size() == capacity_)
throw ;
super::push(_data);
}
private:
int capacity_;
}; void check(int _cap, const std::vector<int>& _data)
{
Stack stack(_cap);
int pushed = ;
for (auto i : _data)
{
if (stack.empty())
{
stack.push(++pushed);
}
if (stack.top() < i)
{
for (int j = pushed + ; j <= i; ++j)
stack.push(j);
pushed = i;
}
if (stack.top() == i)
{
stack.pop();
continue;
}
if (stack.top() > i)
throw ;
}
} int main()
{
int m, n, k;
std::cin >> m >> n >> k;
std::vector<int> data(n);
for (int i = ; i != k; ++i)
try
{
for (int j = ; j != n; ++j)
std::cin >> data[j];
check(m, data);
std::cout << "YES" << std::endl;
}
catch(...)
{
std::cout << "NO" << std::endl;
} return ;
}
题目不难,这里把代码贴出来,是想作个错误示范。算法本身是正确的,能AC,但是设计是不好的:标准库中的容器类不是用来作基类的,因为其析构函数非虚,我写的 Stack 类直接继承 std::stack<int> 是不好的,尽管在这个类设计中,子类的subclass不需要析构,而且整个程序没有作什么派生类到基类的指针转换。我的本意是想实现adapter,为了偷懒就直接公有继承,理应在 Stack 类中包装一个 std::stack<int> 实例。在工程中,公有继承标准库容器就是错误的。
1057:
这次不按题号讲,就是因为这道题要压轴。
初看,不就一个stack和中位数吗,两个我都会写,放到一起我也会写。然后我就写了第一个版本,做了一个 std::stack<int> 的adapter,提供了push、pop、median操作,其中median是把stack拷贝、排序、寻址中位数。感觉很好,样例也过了,想着一遍AC。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm> class Stack
{
public:
Stack() = default;
void push(int i)
{
stack.push_back(i);
}
void pop()
{
if (stack.empty())
std::cout << "Invalid";
else
{
std::cout << stack.back();
stack.pop_back();
}
std::cout << std::endl;
}
void median()
{
if (stack.empty())
std::cout << "Invalid";
else
{
auto temp = stack;
std::sort(temp.begin(), temp.end());
int m = ;
if (temp.size() % )
m = temp[(temp.size() - ) / ];
else
m = temp[temp.size() / - ];
std::cout << m;
}
std::cout << std::endl;
}
private:
std::vector<int> stack;
}; int main()
{
int count;
std::cin >> count;
Stack stack;
for (int i = ; i != count; ++i)
{
std::string instr;
std::cin >> instr;
if (instr == "Push")
{
int i;
std::cin >> i;
stack.push(i);
}
else if (instr == "Pop")
{
stack.pop();
}
else
{
stack.median();
}
}
}
然而并没有。5个case,3个超时。
是 std::cin 读取字符串太慢了吗?换成C的输入输出,还有2个case超时。
然后我想到输入数据有个比较小的范围是有用的,就建了个数组,写了个乱七八糟的访问控制来防止每次遍历都把每个元素访问一遍。没用,超时。
我意识到线性结构不能解决这个问题,不然也对不起这30分了。于是我就想到用 std::map 来存储。先放代码:
#include <iostream>
#include <string>
#include <stack>
#include <map> class Median
{
public:
Median() = default;
int get()
{
return median;
}
void insert(int i)
{
if (median_count == )
{
median = i;
median_count = ;
}
else if (i < median)
++small[i], ++small_count;
else if (i > median)
++large[i], ++large_count;
else
++median_count;
adjust();
}
void erase(int i)
{
if (i < median)
{
--small[i], --small_count;
if (small[i] == )
small.erase(i);
}
else if (i > median)
{
--large[i], --large_count;
if (large[i] == )
large.erase(i);
}
else
--median_count;
adjust();
}
private:
std::map<int, int, std::greater<int>> small;
int small_count = ;
std::map<int, int> large;
int large_count = ;
int median;
int median_count = ;
void small_to_median()
{
median = small.begin()->first;
median_count = small.begin()->second;
small.erase(small.begin());
small_count -= median_count;
}
void large_to_median()
{
median = large.begin()->first;
median_count = large.begin()->second;
large.erase(large.begin());
large_count -= median_count;
}
void median_to_small()
{
small[median] = median_count;
small_count += median_count;
}
void median_to_large()
{
large[median] = median_count;
large_count += median_count;
}
void adjust()
{
if (median_count == )
{
if (small_count < large_count)
large_to_median();
else if (small_count)
small_to_median();
}
else if (small_count + median_count < large_count)
{
median_to_small();
large_to_median();
}
else if (small_count >= median_count + large_count)
{
median_to_large();
small_to_median();
}
}
}; class Stack
{
public:
Stack() = default;
void push(int i)
{
stack_.push(i);
median_.insert(i);
}
void pop()
{
if (stack_.empty())
std::cout << "Invalid";
else
{
int i = stack_.top();
stack_.pop();
std::cout << i;
median_.erase(i);
}
std::cout << std::endl;
}
void median()
{
if (stack_.empty())
std::cout << "Invalid";
else
{
std::cout << median_.get();
}
std::cout << std::endl;
}
private:
std::stack<int> stack_;
Median median_;
}; int main()
{
int count;
std::cin >> count;
Stack stack;
for (int i = ; i != count; ++i)
{
std::string instr;
std::cin >> instr;
if (instr == "Push")
{
int i;
std::cin >> i;
stack.push(i);
}
else if (instr == "Pop")
stack.pop();
else
stack.median();
}
}
这个算法挺复杂的。客户维护一个 Stack 实例,它维护一个 Median 实例,两个都是我自己写的类。Median 中包含两个 std::map<int, int> 实例,分别储存比中位数小的和比中位数大的数。核心算法是 Median::adjust() ,它通过调整两边内容,维护中位数的正确性。不想细说了,看代码吧,只涉及到 std::map 的一些基本操作。
因为两次调整之间只有一个操作,所以可以保证调整一次就好了。
值得吐槽的一点是,C++的输入输出在这道题中比C慢了100ms。然而,算法不对,输入输出再快,也要超时。PAT好像没什么卡输入输出时间的题,毕竟世上除了C和C++还有好多编程语言,要考虑它们的感受。
写完了发现其他博客里没有用这么烦的方法,好像用了什么树状数组,我不会。
try
{
learn_queue.push("树状数组");
}
catch(...)
{
std::cout << "快去睡觉" << std::endl;
} --------------------------------------------------------------------------------
快去睡觉
Program exited with code ...
正好提到树了,下一篇就写树吧。
PAT甲级题分类汇编——理论的更多相关文章
- PAT甲级题分类汇编——杂项
本文为PAT甲级分类汇编系列文章. 集合.散列.数学.算法,这几类的题目都比较少,放到一起讲. 题号 标题 分数 大意 类型 1063 Set Similarity 25 集合相似度 集合 1067 ...
- PAT甲级题分类汇编——图
本文为PAT甲级分类汇编系列文章. 图,就是层序遍历和Dijkstra这一套,#include<queue> 是必须的. 题号 标题 分数 大意 时间 1072 Gas Station 3 ...
- PAT甲级题分类汇编——树
本文为PAT甲级分类汇编系列文章. AVL树好难!(其实还好啦~) 我本来想着今天应该做不完树了,没想到电脑里有一份讲义,PPT和源代码都有,就一遍复习一遍抄码了一遍,更没想到的是编译一遍通过,再没想 ...
- PAT甲级题分类汇编——排序
本文为PAT甲级分类汇编系列文章. 排序题,就是以排序算法为主的题.纯排序,用 std::sort 就能解决的那种,20分都算不上,只能放在乙级,甲级的排序题要么是排序的规则复杂,要么是排完序还要做点 ...
- PAT甲级题分类汇编——计算
本文为PAT甲级分类汇编系列文章. 计算类,指以数学运算为主或为背景的题. 题号 标题 分数 大意 1058 A+B in Hogwarts 20 特殊进制加法 1059 Prime Factors ...
- PAT甲级题分类汇编——线性
本文为PAT甲级分类汇编系列文章. 线性类,指线性时间复杂度可以完成的题.在1051到1100中,有7道: 题号 标题 分数 大意 时间 1054 The Dominant Color 20 寻找出现 ...
- PAT甲级题分类汇编——序言
今天开个坑,分类整理PAT甲级题目(https://pintia.cn/problem-sets/994805342720868352/problems/type/7)中1051~1100部分.语言是 ...
- 【转载】【PAT】PAT甲级题型分类整理
最短路径 Emergency (25)-PAT甲级真题(Dijkstra算法) Public Bike Management (30)-PAT甲级真题(Dijkstra + DFS) Travel P ...
- PAT甲级题解分类byZlc
专题一 字符串处理 A1001 Format(20) #include<cstdio> int main () { ]; int a,b,sum; scanf ("%d %d& ...
随机推荐
- Mitmproxy的mimdump mimproxy mitmweb命令行性能对比
引用官方的原话如下 Very cool test - thanks for sharing!
- 在java的Map集合中,怎样更改value的值
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/chenyao1994/article/de ...
- Mybatis-Generator开发教程
转载:https://blog.csdn.net/qqyb2000/article/details/80031559 MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyB ...
- 微信小程序-收货地址左滑删除
我参照了其中的部分代码,如:bindtouchstart,bindtouchmove,bindtouchend事件多数组中偏移值的更改, 在结合微信 movable-area 和 movable-vi ...
- Android命名规范(重点讲解:包名)
Android程序开发中,使用规范的命名有益于程序的开发和后期阅读.本文主要对Android程序包名的定义做详细介绍,并附带一些简单的命名规则. 一.标识符命名方法1 .小驼峰命名法,除首单词外,其余 ...
- Linux Nginx naxsi
nginx naxsi 模块 - 简书https://www.jianshu.com/p/8492da04b3ba naxsi compile · nbs-system/naxsi Wikihttps ...
- APP:目录
ylbtech-APP:目录 1.返回顶部 2.返回顶部 3.返回顶部 4.返回顶部 5.返回顶部 6.返回顶部 7.返回顶部 8.返回顶部 9.返回顶部 ...
- functools.partial偏函数的使用
https://docs.python.org/3.6/library/functools.html 从名字可以看出,该函数的作用就是部分使用某个函数,即冻结住某个函数的某些参数,让它们保证为某个值, ...
- 【Spring】基于@Aspect的AOP配置
Spring AOP面向切面编程,可以用来配置事务.做日志.权限验证.在用户请求时做一些处理等等.用@Aspect做一个切面,就可以直接实现. · 本例演示一个基于@Aspect的小demo 1. ...
- ANSI转义序列
http://ascii-table.com/ansi-escape-sequences.php 制作控制台程序时, 要实现一些特殊效果, 需要了解一下 [ANSI转义序列] . ANSI转义序列是一 ...