A - 咕咕东的目录管理器
题面 咕咕东的雪梨电脑的操作系统在上个月受到宇宙射线的影响,时不时发生故障,他受不了了,想要写一个高效易用零bug的操作系统 ——
这工程量太大了,所以他定了一个小目标,从实现一个目录管理器开始。前些日子,东东的电脑终于因为过度收到宇宙射线的影响而宕机,无法写代码。他的好友TT正忙着在B站看猫片,另一位好友瑞神正忙着打守望先锋。现在只有你能帮助东东!初始时,咕咕东的硬盘是空的,命令行的当前目录为根目录 root。
目录管理器可以理解为要维护一棵有根树结构,每个目录的儿子必须保持字典序。
现在咕咕东可以在命令行下执行以下表格中描述的命令:
命令 类型 实现 说明 MKDIR s 操作 在当前目录下创建一个子目录 s,s 是一个字符串 创建成功输出
“OK”;若当前目录下已有该子目录则输出 “ERR” RM s 操作 在当前目录下删除子目录 s,s 是一个字符串 删除成功输出
“OK”;若当前目录下该子目录不存在则输出 “ERR” CD s 操作 进入一个子目录 s,s
是一个字符串(执行后,当前目录可能会改变) 进入成功输出 “OK”;若当前目录下该子目录不存在则输出 “ERR” 特殊地,若 s 等于
“…” 则表示返回上级目录,同理,返回成功输出 “OK”,返回失败(当前目录已是根目录没有上级目录)则输出 “ERR”
SZ 询问 输出当前目录的大小 也即输出 1+当前目录的子目录数 LS 询问 输出多行表示当前目录的 “直接子目录”
名 若没有子目录,则输出 “EMPTY”;若子目录数属于 [1,10] 则全部输出;若子目录数大于 10,则输出前 5 个,再输出一行
“…”,输出后 5 个。 TREE 询问 输出多行表示以当前目录为根的子树的前序遍历结果 若没有后代目录,则输出
“EMPTY”;若后代目录数+1(当前目录)属于 [1,10] 则全部输出;若后代目录数+1(当前目录)大于 10,则输出前 5
个,再输出一行 “…”,输出后 5 个。若目录结构如上图,当前目录为 “root” 执行结果如下, UNDO 特殊 撤销操作 撤销最近一个
“成功执行” 的操作(即MKDIR或RM或CD)的影响,撤销成功输出 “OK” 失败或者没有操作用于撤销则输出 “ERR” 输入
输入文件包含多组测试数据,第一行输入一个整数表示测试数据的组数 T (T <= 20);每组测试数据的第一行输入一个整数表示该组测试数据的命令总数 Q (Q <= 1e5);
每组测试数据的 2 ~ Q+1 行为具体的操作 (MKDIR、RM 操作总数不超过 5000);
面对数据范围你要思考的是他们代表的 “命令” 执行的最大可接受复杂度,只有这样你才能知道你需要设计的是怎样复杂度的系统。 输出
每组测试数据的输出结果间需要输出一行空行。注意大小写敏感。时空限制 Time limit 6000 ms
Memory limit 1048576 kB
样例输入 1 22 MKDIR dira CD dirb CD dira MKDIR a MKDIR b MKDIR c CD …
MKDIR dirb CD dirb MKDIR x CD … MKDIR dirc CD dirc MKDIR y CD … SZ
LS TREE RM dira TREE UNDO TREE 样例输出 OK ERR OK OK OK OK OK OK OK OK OK
OK OK OK OK 9 dira dirb dirc root dira a b c dirb x dirc y OK root
dirb x dirc y OK root dira a b c dirb x dirc y hint 英文原版题面:
思路;大致操作:创建结构体保存每一个节点的信息,注意采用map记录此节点的子节点,map将建立节点名与节点序号的一一映射,可以通过目录名很快的找到该节点对应的序号,时间为log级,并且map会自动排序,用fa记录父节点的序号,用sz表示以此节点为根的子树的总大小。 对于mkdir操作,即当前目录下创建一个新目录,所以我们需要一个整型now来表示当前目录是第几个目录了。将刚刚执行成功的这条指令和与它相关的节点及其父节点加入到一个vector结构体中, 该对象存储已经成功执行的命令。在rm操作,和mikdir操作的框架差不多。在cd操作中,进入一个新目录,在操作成功后,更改now,now为刚进入的节点序号。对于后三个操作,sz,ls,tree,直接调用函数,不需要其他改变。对于undo操作,则从vector中取出最后一条指令,如果为mkdir,则将新建的目录删除,如果为rm,则将之前删除的目录加上,如果为cd,则返回上一级。考虑每一步的具体的操作,对于mkdir操作,则是现在当前目录下查找要添加的目录,如果当前目录下没有则再进行添加,并且向上维护每一个目录的sz值。对于rm操作,也是首先查找,当找到之后再进行删除,并且向上维护个目录的sz值。对于cd操作,则我们先判断是否参数为"…",如果是,则进入父亲目录,否则进入子目录。对于sz操作,直接返回结构体中的sz。对于ls操作,则要首先判断当前目录下有几个子目录,如果小于10个,则全部输出,否则输出前5个和后5个,中间用"…"隔开。为了配合undo操作,还有一个操作时将删除的目录添加上,对于最后一个操作tree,如果我们执行tree操作都将整个文件目录遍历一遍,肯定会超时。在有些情况下是不需要重新遍历,比如两次tree操作时的目录结构相同,则我们直接输出上次调用tree的结果就可以。则我们可以通过一个bool类型的变量来标记文件目录在一次tree操作后是否更改过。如果没有更改过,则直接输出已经保存好的结果。如果更改过,则要对文件目录进行遍历,如果该文件下的目录个数小于10个,则直接进行遍历,将结果存在vector中。如果大于10个,则我们只需要前5个和后5个,所以需两个遍历函数来分别进行操作,第一次遍历得到前5个目录,第二次遍历得到后5个目录。
#include <iostream>
#include <string>
#include<cstring>
#include <map>
#include <vector>
using namespace std;
int now, cnt;
struct v_node{
string name; //操作
int now; //当前目录
int next; //要添加 删除 移动的目录位置
};
vector<v_node> v;
struct directory{
string name;
map<string, int>mp; //后代的名称和编号
int fa, sz;
vector<string> pre, bck;
bool tag;
};
directory node[100010];
void update(int id, int num) //将变化数据向上传导
{
while (id != -1){
node[id].tag = 0;
node[id].sz += num;
id = node[id].fa;
}
}
void mkdir(string& s)
{
if (node[now].mp.count(s) == 1) cout << "ERR" << endl;
else{
node[now].mp.insert(pair<string, int>(s, cnt));
update(now, 1);
node[cnt].name = s;
node[cnt].fa = now;
node[cnt].sz = 1;
node[cnt].tag = 0;
node[cnt].mp.clear();
node[cnt].pre.clear();
node[cnt].bck.clear();
v_node p;
p.name = "MKDIR";
p.now = now;
p.next = cnt;
v.push_back(p);
cnt++;
cout << "OK" << endl;
}
}
void rm(string& name)
{
if (node[now].mp.count(name) == 1) {
int i = node[now].mp[name];
node[now].mp.erase(name);
update(now, (-1) * node[i].sz);
v_node p;
p.name = "RM";
p.now = now;
p.next = i;
v.push_back(p);
cout << "OK" << endl;
}
else cout << "ERR" << endl;
}
void cd(string& name)
{
if (name == ".."){
if (node[now].fa == -1)
cout << "ERR" << endl;
else{
v_node p;
p.name = "CD";
p.now = now;
p.next = node[now].fa;
v.push_back(p);
now = node[now].fa;
cout << "OK" << endl;
}
}
else {
if (node[now].mp.count(name) == 1) {
v_node p;
p.name = "CD", p.now = now, p.next = node[now].mp[name];
v.push_back(p);
now = node[now].mp[name];
cout << "OK" << endl;
}
else cout << "ERR" << endl;
}
}
void sz()
{
cout << node[now].sz << endl;
}
void ls()
{
if (node[now].mp.size() == 0) cout << "EMPTY" << endl;
else if (node[now].mp.size() >= 1 && node[now].mp.size() <= 10) {
for (map<string, int>::iterator it = node[now].mp.begin(); it != node[now].mp.end(); it++) cout << it->first << endl;
}
else
{ //前5个
map<string, int>::iterator it = node[now].mp.begin();
for (int i = 1; i <= 5; i++){
cout << it->first << endl;
it++;
}
cout << "..." << endl;
//后5个
it = node[now].mp.end();
for (int i = 1; i <= 5; i++) it--;
for (int i = 1; i <= 5; i++){
cout << it->first << endl;
it++;
}
}
}
void track(int id, vector<string>& pre)
{
pre.push_back(node[id].name);
for (map<string, int>::iterator it = node[id].mp.begin(); it != node[id].mp.end(); it++) track(it->second, pre);
}
void pretrack(int id, vector<string>& pre, int& num)
{
pre.push_back(node[id].name);
num--;
if (num == 0) return;
for (map<string, int>::iterator it = node[id].mp.begin(); it != node[id].mp.end(); it++){
pretrack(it->second, pre, num);
if (num == 0) break;
}
}
void bcktrack(int id, vector<string>& bck, int& num)
{
map<string, int>::iterator it = node[id].mp.end();
int n = node[id].mp.size();
for (int i = 0; i < n; i++){
it--;
bcktrack(it->second, bck, num);
if (num == 0) return;
}
bck.push_back(node[id].name);
num--;
}
void pushdown(int id) //遍历子树
{
node[id].pre.clear();
node[id].bck.clear();
if (node[id].sz <= 10) track(id, node[id].pre);
else{
int n = 5;
pretrack(id, node[id].pre, n);
n = 5;
bcktrack(id, node[id].bck, n);
}
node[id].tag = 1;
}
void tree()
{
if (!node[now].tag) pushdown(now);
if (node[now].sz == 1) cout << "EMPTY" << endl;
else if (node[now].sz > 1 && node[now].sz <= 10){
for (int i = 0; i < node[now].pre.size(); i++) cout << node[now].pre[i] << endl;
}
else{
for (int i = 0; i < 5; i++) cout << node[now].pre[i] << endl;
cout << "..." << endl;
for (int i = 4; i >= 0; i--) cout << node[now].bck[i] << endl;
}
}
void undo()
{
if (v.size() == 0) cout << "ERR" << endl;
else{
v_node p = v[v.size() - 1];
v.pop_back();
cout << "OK" << endl;
if (p.name == "MKDIR") {
update(p.now, (-1) * node[p.next].sz);
node[p.now].mp.erase(node[p.next].name);
}
else if (p.name == "RM"){
update(p.now, node[p.next].sz);
node[p.now].mp[node[p.next].name] = p.next;
}
else now = p.now;
}
}
int main()
{
int T;
cin >> T;
for (int i = 0; i < T; i++){
if (i != 0) cout << endl;
int Q;
cin >> Q;
now = 0;
cnt = 1;
node[now].name = "root";
node[now].fa = -1;
node[now].sz = 1;
node[now].tag = 0;
node[now].mp.clear(),node[now].pre.clear(), node[now].bck.clear();
v.clear();
for (int j = 0; j < Q; j++) {
string cmd;
cin >> cmd;
string s;
if (cmd == "MKDIR"){
cin >> s;
mkdir(s);
}
else if (cmd == "RM"){
cin >> s;
rm(s);
}
else if (cmd == "CD"){
cin >> s;
cd(s);
}
else if (cmd == "SZ") sz();
else if (cmd == "LS") ls();
else if (cmd == "TREE") tree();
else if (cmd == "UNDO") undo();
}
}
}
B - 东东学打牌
题面 最近,东东沉迷于打牌。所以他找到 HRZ、ZJM 等人和他一起打牌。由于人数众多,东东稍微修改了亿下游戏规则:
所有扑克牌只按数字来算大小,忽略花色。 每张扑克牌的大小由一个值表示。A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J,
Q, K 分别指代 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13。 每个玩家抽得 5
张扑克牌,组成一手牌!(每种扑克牌的张数是无限的,你不用担心,东东家里有无数副扑克牌) 理所当然地,一手牌是有不同类型,并且有大小之分的。举个栗子,现在东东的 “一手牌”(记为 α),瑞神的 “一手牌”(记为 β),要么 α > β,要么 α < β,要么 α = β。
那么这两个 “一手牌”,如何进行比较大小呢?首先对于不同类型的一手牌,其值的大小即下面的标号;对于同类型的一手牌,根据组成这手牌的 5
张牌不同,其值不同。下面依次列举了这手牌的形成规则:大牌:这手牌不符合下面任一个形成规则。如果 α 和 β 都是大牌,那么定义它们的大小为组成这手牌的 5 张牌的大小总和。
对子:5 张牌中有 2 张牌的值相等。如果 α 和 β 都是对子,比较这个 “对子” 的大小,如果 α 和 β 的 “对子”
大小相等,那么比较剩下 3 张牌的总和。两对:5 张牌中有两个不同的对子。如果 α 和 β
都是两对,先比较双方较大的那个对子,如果相等,再比较双方较小的那个对子,如果还相等,只能比较 5 张牌中的最后那张牌组不成对子的牌。三个:5 张牌中有 3 张牌的值相等。如果 α 和 β 都是 “三个”,比较这个 “三个” 的大小,如果 α 和 β 的 “三个”
大小相等,那么比较剩下 2 张牌的总和。三带二:5 张牌中有 3 张牌的值相等,另外 2 张牌值也相等。如果 α 和 β 都是 “三带二”,先比较它们的 “三个”
的大小,如果相等,再比较 “对子” 的大小。炸弹:5 张牌中有 4 张牌的值相等。如果 α 和 β 都是 “炸弹”,比较 “炸弹” 的大小,如果相等,比较剩下那张牌的大小。
顺子:5 张牌中形成 x, x+1, x+2, x+3, x+4。如果 α 和 β 都是 “顺子”,直接比较两个顺子的最大值。
龙顺:5 张牌分别为 10、J、Q、K、A。
作为一个称职的魔法师,东东得知了全场人手里 5 张牌的情况。他现在要输出一个排行榜。排行榜按照选手们的 “一手牌”
大小进行排序,如果两个选手的牌相等,那么人名字典序小的排在前面。不料,此时一束宇宙射线扫过,为了躲避宇宙射线,东东慌乱中清空了他脑中的 Cache。请你告诉东东,全场人的排名
输入 输入包含多组数据。每组输入开头一个整数 n (1 <= n <= 1e5),表明全场共多少人。 随后是 n 行,每行一个字符串 s1
和 s2 (1 <= |s1|,|s2| <= 10), s1 是对应人的名字,s2 是他手里的牌情况。输出 对于每组测试数据,输出 n 行,即这次全场人的排名。
样例输入 3 DongDong AAA109 ZJM 678910 Hrz 678910
样例输出 Hrz ZJM DongDong
思路:1、记录所有人手中牌的情况(使用结构体存储),在结构体内定义牌的大小比较函数
2、用sort函数比较牌的大小
3、依次输出所有牌
#include<string>
#include<cstring>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<set>
#include<stdio.h>
using namespace std;
int n;
struct Play {
string name;//姓名
int total_class;//牌的总类别
int per_value[5];//记录牌所拥有的每一类别及每一类别的个数
bool operator<(Play& p)const
{
if (total_class != p.total_class) return total_class < p.total_class;
if (per_value[0] != p.per_value[0]) return per_value[0] < p.per_value[0];
if (per_value[1] != p.per_value[1]) return per_value[1] < p.per_value[1];
if (per_value[2] != p.per_value[2]) return per_value[2] < p.per_value[2];
return name > p.name;
}
};
Play player[100010];
string s[100010];
int main()
{
ios::sync_with_stdio(false);
while (cin>>n) {
for (int i = 0; i < n; i++) cin >> player[i].name >> s[i];
for (int i = 0; i < n; i++) {
int s_num = s[i].size();
int a[5];
int jj = 0;
for (int j = 0; j < s_num; j++, jj++) {
if (s[i][j] == '1') {
a[jj] = 10;
j++;
}
else if (s[i][j] == 'A')a[jj] = 1;
else if (s[i][j] == 'J')a[jj] = 11;
else if (s[i][j] == 'Q')a[jj] = 12;
else if (s[i][j] == 'K')a[jj] = 13;
else a[jj] = (int)(s[i][j] - '0');
}
sort(a, a + 5);
set<int> a_kind;
for (int j = 0; j < 5; j++) {
player[i].per_value[j] = 0;
a_kind.insert(a[j]);
}
int a_num = a_kind.size();
switch (a_num)
{
case 1:
{
player[i].total_class = 6;
player[i].per_value[0] = a[0];
player[i].per_value[1] = a[4];
break;
}
case 2:
{
if (a[0] == a[1] && a[0] == a[2] && a[0] == a[3]) {//4带1
player[i].total_class = 6;
player[i].per_value[0] = a[0];
player[i].per_value[1] = a[4];
}
else if (a[1] == a[2] && a[1] == a[3] && a[1] == a[4]) {
player[i].total_class = 6;
player[i].per_value[0] = a[1];
player[i].per_value[1] = a[0];
}
else if (a[0] == a[1] && a[0] == a[2]) {//3带2
player[i].total_class = 5;
player[i].per_value[0] = a[0];
player[i].per_value[1] = a[3];
}
else {
player[i].total_class = 5;
player[i].per_value[0] = a[2];
player[i].per_value[1] = a[0];
}
break;
}
case 3:
{
if (a[0] == a[1] && a[0] == a[2]) {//3 1 1
player[i].total_class = 4;
player[i].per_value[0] = a[0];
player[i].per_value[1] = a[3] + a[4];
}
else if (a[1] == a[2] && a[1] == a[3]) {//1 3 1
player[i].total_class = 4;
player[i].per_value[0] = a[1];
player[i].per_value[1] = a[0] + a[4];
}
else if (a[2] == a[3] && a[2] == a[4]) {//1 1 3
player[i].total_class = 4;
player[i].per_value[0] = a[2];
player[i].per_value[1] = a[0] + a[1];
}
else if (a[0] == a[1] && a[2] == a[3]) {//2 2 1
player[i].total_class = 3;
player[i].per_value[0] = a[2];
player[i].per_value[1] = a[0];
player[i].per_value[2] = a[4];
}
else if (a[0] == a[1] && a[3] == a[4]) {//2 1 2
player[i].total_class = 3;
player[i].per_value[0] = a[3];
player[i].per_value[1] = a[0];
player[i].per_value[2] = a[2];
}
else {
player[i].total_class = 3;//1 2 2
player[i].per_value[0] = a[3];
player[i].per_value[1] = a[1];
player[i].per_value[2] = a[0];
}
break;
}
case 4:
{
player[i].total_class = 2;
if (a[0] == a[1]) {
player[i].per_value[0] = a[0];
player[i].per_value[1] = a[2] + a[3] + a[4];
}
else if (a[1] == a[2]) {
player[i].per_value[0] = a[1];
player[i].per_value[1] = a[0] + a[3] + a[4];
}
else if (a[2] == a[3]) {
player[i].per_value[0] = a[2];
player[i].per_value[1] = a[0] + a[1] + a[4];
}
else {
player[i].per_value[0] = a[3];
player[i].per_value[1] = a[0] + a[1] + a[2];
}
break;
}
case 5:
{
if (a[0] == 1 && a[1] == 10 && a[2] == 11 && a[3] == 12 && a[4] == 13) player[i].total_class = 8;
else if (a[0] == a[1] - 1 && a[0] == a[2] - 2 && a[0] == a[3] - 3 && a[0] == a[4] - 4) {
player[i].total_class = 7;
player[i].per_value[0] = a[4];
}
else {
player[i].total_class = 1;
player[i].per_value[0] = a[0] + a[1] + a[2] + a[3] + a[4];
}
break;
}
default:
break;
}
// for (int mm = 0; mm < 5; mm++)cout << a[mm] << " ";
// cout << endl;
// cout << player[i].name << " total_class = " << player[i].total_class << " " << player[i].per_value[0] << " " << player[i].per_value[1] << " " << player[i].per_value[2] << endl;
}
sort(player, player + n);
for (int i = n - 1; i >= 0; i--) cout << player[i].name << endl;
}
return 0;
}
C - 签到题,独立思考哈
SDUQD 旁边的滨海公园有 x 条长凳。第 i 个长凳上坐着 a_i 个人。这时候又有 y
个人将来到公园,他们将选择坐在某些公园中的长凳上,那么当这 y 个人坐下后,记k =
所有椅子上的人数的最大值,那么k可能的最大值mx和最小值mn分别是多少。Input 第一行包含一个整数 x (1 <= x <= 100) 表示公园中长椅的数目 第二行包含一个整数 y (1 <= y <=
1000) 表示有 y 个人来到公园 接下来 x 个整数 a_i (1<=a_i<=100),表示初始时公园长椅上坐着的人数Output 输出 mn 和 mx
Input Example 3 7 1 6 1
Output Example 6 13
样例解释 最初三张椅子的人数分别为 1 6 1 接下来来了7个人。 可能出现的情况为{1 6 8},{1,7,7},…,{8,6,1}
相对应的k分别为8,7,…,8 其中,状态{1,13,1}的k = 13,为mx状态{4,6,5}和状态{5,6,4}的k = 6,为mn
思路:mx=原来长椅上人数的最大值+新来的所有人,
如果新来的人<原来椅子上的最多人数×椅子数-原来的总人数,i定义为(新来的人-(椅子上的最多人数×椅子数-原来的总人数)除以椅子数)的向上取整。
mn=椅子上的最多人数;否则mn=椅子上的最多人数+i。
#include<iostream>
using namespace std;
int main()
{
int x, y, a[110];//x长椅的数量,y新来的人,a[i]初始时在长椅上坐着的人
cin >> x >> y;
int sum_ai = 0;
int max_ai = 0;
int sum_max_ai = 0;
for (int i = 0; i < x; i++) {
cin >> a[i];
if (a[i] > max_ai) max_ai = a[i];
sum_ai += a[i];
}
int mx = max_ai + y;
sum_max_ai = max_ai * x;
if (sum_ai + y <= sum_max_ai) {
cout << max_ai << " ";
}
else {
int i;
int remainder_ = sum_ai + y - sum_max_ai;
for (i = 1;; i++) {
if (i * x >= remainder_) {
break;
}
}
cout << i + max_ai << " ";
}
cout << mx << endl;
return 0;
}