A - 咕咕东的目录管理器
咕咕东的雪梨电脑的操作系统在上个月受到宇宙射线的影响,时不时发生故障,他受不了了,想要写一个高效易用零bug的操作系统 —— 这工程量太大了,所以他定了一个小目标,从实现一个目录管理器开始。前些日子,东东的电脑终于因为过度收到宇宙射线的影响而宕机,无法写代码。他的好友TT正忙着在B站看猫片,另一位好友瑞神正忙着打守望先锋。现在只有你能帮助东东!
初始时,咕咕东的硬盘是空的,命令行的当前目录为根目录 root。
目录管理器可以理解为要维护一棵有根树结构,每个目录的儿子必须保持字典序。
现在咕咕东可以在命令行下执行以下表格中描述的命令:
Input
输入文件包含多组测试数据,第一行输入一个整数表示测试数据的组数 T (T <= 20);
每组测试数据的第一行输入一个整数表示该组测试数据的命令总数 Q (Q <= 1e5);
每组测试数据的 2 ~ Q+1 行为具体的操作 (MKDIR、RM 操作总数不超过 5000);
面对数据范围你要思考的是他们代表的 “命令” 执行的最大可接受复杂度,只有这样你才能知道你需要设计的是怎样复杂度的系统。
Output
每组测试数据的输出结果间需要输出一行空行。注意大小写敏感。
Tips
Time limit 6000 ms
Memory limit 1048576 kB
Examples
input
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
output
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
题目解析
- 首先设计解题的框架
- 由于题目中目录见有明显的父子关系,所以将目录设定为一种数据结构,用树的方式来存储整个目录结构。由于操作包括命令和对象所以也定义为一个结构体。
struct Directory {
Directory* parent;
map<string,Directory*> children;
string name;
int treesize;
};
struct Operation {
int type;
string object;
Directory* res;
}success[maxn];
- 设置执行函数,根据操作的命令和操作对象执行命令
void execute()
{
switch (ope.type)
{
case 1:
ope.res = current_dir->mkdir(ope.object);
if (ope.res == nullptr)
cout << "ERR" << endl;
else
{
cout << "OK" << endl;
success[tot++] = ope;
}
break;
case 2:
ope.res = current_dir->rm(ope.object);
if (ope.res == nullptr)
cout << "ERR" << endl;
else
{
cout << "OK" << endl;
success[tot++] = ope;
}
break;
case 3:
ope.res = current_dir;
tmp = current_dir->cd(ope.object);
if (tmp == nullptr)
cout << "ERR" << endl;
else
{
current_dir = tmp;
cout << "OK" << endl;
success[tot++] = ope;
}
break;
case 4:
cout << current_dir->sz() << endl;
break;
case 5:
current_dir->ls();
break;
case 6:
current_dir->tree();
break;
case 7:
if (undo())
cout << "OK" << endl;
else cout << "ERR" << endl;
break;
}
}
- 基于面向对象的思想,将mkdir、cd等操作的函数封装在结构体内。
- MKDIR:首先检查是否存在该子目录,不存在就创建,返回创建的目录或空指针。
Directory* mkdir(string nam)
{
map<string,Directory*>::iterator ans = children.find(nam);
if (ans != children.end())
return nullptr;
updated = true;
Directory* newdir = new Directory(this, nam);
children.insert(make_pair(nam, newdir));
addtreesize(newdir, 1);
return newdir;
}
RM:根据名字,查找子目录并删除,返回删除目录或空指针
Directory* rm(string nam)
{
map<string, Directory*>::iterator ans = children.find(nam);
if (ans == children.end())
return nullptr;
updated = true;
Directory* del = ans->second;
addtreesize(ans->second, -del->treesize);
children.erase(ans);
return del;
}
CD:CD命令的操作对象有两种,普通的目录和“…”,对普通目录就切换当前目录,如果是根目录,则返回空指针,反之则返回结果。
Directory* cd(string nam)
{
if (nam == "..")
{
if (this->parent != nullptr)
updated = true;
return this->parent;
}
map<string, Directory*>::iterator ans = children.find(nam);
if (ans == children.end())
return nullptr;
updated = true;
return ans->second;
}
SZ:返回当前目录大小
int sz()
{
return treesize;
}
LS:分三种情况,子目录为空时,输出empty。子目录个数小于十个,输出所有子目录,超过十个输出前五个和后五个。
void ls()
{
if (children.size() == 0)
cout << "EMPTY" << endl;
else if (children.size() <= 10)
for (auto i = children.begin(); i != children.end(); i++)
cout << i->first << endl;
else
{
auto p = children.begin();
for (int i = 0;i < 5; i++, p++)
cout << p->first << endl;
cout << "..." << endl;
p = children.end();
for (int i = 0; i < 5; i++)
p--;
while (p!=children.end())
{
cout << p->first << endl;
p++;
}
}
}
TREE:由于其复杂度,有可能会超时,所以采用缓存的方式来提高速度。如果没有更新,则根据最后一次的输出情况输出结果。当没有后代时,返回空。树中元素数少于10,则前序输出所有节点。树中元素个数大于10个,则先前序遍历5个,在后序遍历并倒序填入最后五个,最后输出答案。
void tree()
{
if (!updated)
{
if (con == -1)
{
for (int i = 0; i < 5; i++)
cout << res[i] << endl;
cout << "..." << endl;
for (int i = 5; i < 10; i++)
cout << res[i] << endl;
}
else if (con == 0)
cout << "EMPTY" << endl;
else for (int i = 0; i < con; i++)
cout << res[i] << endl;
return;
}
updated = false;
if (treesize == 1)
{
con = 0;
cout << "EMPTY" << endl;
}
else if (treesize <= 10)
{
con = 0;
preorderall(this);
}
else
{
con = 0;
num = 0;
preorder(this);
con = 9;
num = 0;
backorder(this);
con = -1;
for (int i = 0; i < 5; i++)
cout << res[i] << endl;
cout << "..." << endl;
for (int i = 5; i < 10; i++)
cout << res[i] << endl;
}
}
UNDO:每次成功的操作都会被保存起来,根据成功的操作撤销其影响,当上一个是mkdir时,很简单删除就行了,当上一个是rm时,首先要将目录重新挂在原本的父目录下,接着要修改沿着根节点到删除元素的路径修改每棵受影响树的元素个数,当上一个是cd,则修改当前目录即可,最后修改updated,置为true。
bool undo()
{
if (tot == 0)
return false;
switch (success[--tot].type)
{
case 1:
current_dir->rm(success[tot].object);
break;
case 2:
current_dir->add(success[tot].object, success[tot].res);
break;
case 3:
current_dir = success[tot].res;
break;
}
updated = true;
return true;
}
完整代码
#include<iostream>
#include<map>
using namespace std;
int T, Q;
const int maxn = 1e5 + 10;
const map<string, int> Type = { {"MKDIR",1},{"RM",2},{"CD",3},{"SZ",4},{"LS",5},{"TREE",6},{"UNDO",7} };
string res[20];
int con, updated = 1;
int tot;
struct Directory {
Directory* parent;
map<string,Directory*> children;
string name;
int treesize;
Directory(Directory* parent,string name)
{
this->parent = parent;
this->name = name;
treesize = 1;
}
Directory* mkdir(string nam)
{
map<string,Directory*>::iterator ans = children.find(nam);
if (ans != children.end())
return nullptr;
updated = true;
Directory* newdir = new Directory(this, nam);
children.insert(make_pair(nam, newdir));
addtreesize(newdir, 1);
return newdir;
}
Directory* rm(string nam)
{
map<string, Directory*>::iterator ans = children.find(nam);
if (ans == children.end())
return nullptr;
updated = true;
Directory* del = ans->second;
addtreesize(ans->second, -del->treesize);
children.erase(ans);
return del;
}
Directory* cd(string nam)
{
if (nam == "..")
{
if (this->parent != nullptr)
updated = true;
return this->parent;
}
map<string, Directory*>::iterator ans = children.find(nam);
if (ans == children.end())
return nullptr;
updated = true;
return ans->second;
}
int sz()
{
return treesize;
}
void ls()
{
if (children.size() == 0)
cout << "EMPTY" << endl;
else if (children.size() <= 10)
for (auto i = children.begin(); i != children.end(); i++)
cout << i->first << endl;
else
{
auto p = children.begin();
for (int i = 0;i < 5; i++, p++)
cout << p->first << endl;
cout << "..." << endl;
p = children.end();
for (int i = 0; i < 5; i++)
p--;
while (p!=children.end())
{
cout << p->first << endl;
p++;
}
}
}
void tree()
{
if (!updated)
{
if (con == -1)
{
for (int i = 0; i < 5; i++)
cout << res[i] << endl;
cout << "..." << endl;
for (int i = 5; i < 10; i++)
cout << res[i] << endl;
}
else if (con == 0)
cout << "EMPTY" << endl;
else for (int i = 0; i < con; i++)
cout << res[i] << endl;
return;
}
updated = false;
if (treesize == 1)
{
con = 0;
cout << "EMPTY" << endl;
}
else if (treesize <= 10)
{
con = 0;
preorderall(this);
}
else
{
con = 0;
num = 0;
preorder(this);
con = 9;
num = 0;
backorder(this);
con = -1;
for (int i = 0; i < 5; i++)
cout << res[i] << endl;
cout << "..." << endl;
for (int i = 5; i < 10; i++)
cout << res[i] << endl;
}
}
void add(string nam,Directory* dir)
{
dir->parent = this;
children.insert(make_pair(nam, dir));
addtreesize(dir, dir->treesize);
}
void clear()
{
for (auto i = children.begin(); i != children.end(); i++)
{
i->second->clear();
delete i->second;
}
children.clear();
}
private:
int num = 0;
void addtreesize(Directory*dir,int dnum)
{
while (dir->parent != nullptr)
{
dir = dir->parent;
dir->treesize += dnum;
}
}
void preorderall(Directory* dir)
{
cout << dir->name << endl;
res[con++] = dir->name;
for (auto i = dir->children.begin(); i != dir->children.end(); i++)
preorderall(i->second);
}
void preorder(Directory* dir)
{
if (num == 5)
return;
res[con++] = dir->name;
num++;
for (auto i = dir->children.begin(); i != dir->children.end(); i++)
preorder(i->second);
}
void backorder(Directory* dir)
{
for (auto i = dir->children.rbegin(); i != dir->children.rend(); i++)
backorder(i->second);
if (num == 5)
return;
res[con--] = dir->name;
num++;
}
};
struct Operation {
int type;
string object;
Directory* res;
}success[maxn];
Directory* current_dir;
Directory* tmp;
Operation ope;
bool undo()
{
if (tot == 0)
return false;
switch (success[--tot].type)
{
case 1:
current_dir->rm(success[tot].object);
break;
case 2:
current_dir->add(success[tot].object, success[tot].res);
break;
case 3:
current_dir = success[tot].res;
break;
}
updated = true;
return true;
}
void execute()
{
switch (ope.type)
{
case 1:
ope.res = current_dir->mkdir(ope.object);
if (ope.res == nullptr)
cout << "ERR" << endl;
else
{
cout << "OK" << endl;
success[tot++] = ope;
}
break;
case 2:
ope.res = current_dir->rm(ope.object);
if (ope.res == nullptr)
cout << "ERR" << endl;
else
{
cout << "OK" << endl;
success[tot++] = ope;
}
break;
case 3:
ope.res = current_dir;
tmp = current_dir->cd(ope.object);
if (tmp == nullptr)
cout << "ERR" << endl;
else
{
current_dir = tmp;
cout << "OK" << endl;
success[tot++] = ope;
}
break;
case 4:
cout << current_dir->sz() << endl;
break;
case 5:
current_dir->ls();
break;
case 6:
current_dir->tree();
break;
case 7:
if (undo())
cout << "OK" << endl;
else cout << "ERR" << endl;
break;
}
}
int main()
{
string t,o;
cin >> T;
while (T--)
{
current_dir = new Directory(nullptr, "root");
cin >> Q;
while (Q--)
{
cin >> t;
ope.type = Type.find(t)->second;
if (ope.type == 1 || ope.type == 2 || ope.type == 3)
{
cin >> o;
ope.object = o;
}
execute();
}
tot = 0;
updated = true;
con = 0;
if (T != 0)
cout << endl;
}
return 0;
}
note
- 要学会估计复杂度,本次题目里如果不采用缓存,最长可能需要10s,显然会超时,所以要设计一些方法来减少时间。
- t3的题目要一步一步的完善,所以数据结构至关重要,要设计好数据结构。如果中间思路错了,可以修改,但是一旦数据结构出现问题,就可能全部推翻重来。
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。请你告诉东东,全场人的排名
Input
输入包含多组数据。每组输入开头一个整数 n (1 <= n <= 1e5),表明全场共多少人。
随后是 n 行,每行一个字符串 s1 和 s2 (1 <= |s1|,|s2| <= 10), s1 是对应人的名字,s2 是他手里的牌情况。
Output
对于每组测试数据,输出 n 行,即这次全场人的排名。
题目分析
- 先设置数据结构,定义结构体person
struct Person {
string name;
int value1,value2;
bool operator<(const Person& p)const
{
if (value1 != p.value1)
return value1 > p.value1;
if (value2 != p.value2)
return value2 > p.value2;
return cmp(name, p.name);
}
- 设计解题框架,首先输入一个人的信息,然后将信息存入数组中,存入过程中要计算出牌的类型及牌的大小,最后计算这些信息要通过具体的九条规则来测试。
完整代码
#include<iostream>
#include<string>
#include<map>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 1e5 + 10;
int n,tot = 0;
int value1, value2;
int pai[6];
string s1, s2;
const map<char, int> tran = { {'A',1},{'2',2},{'3',3},{'4',4},{'5',5},{'6',6},{'7',7},{'8',8},{'9',9},{'J',11},{'Q',12},{'K',13} };
bool cmp(string S1,string S2)
{
int Min = min(S1.size(), S2.size());
for (int i = 0; i < Min; i++)
{
int x1 = toupper(S1.at(i));
int x2 = toupper(S2.at(i));
if (x1 != x2)
return x1 < x2;
}
return S1.size() < S2.size();
}
struct Person {
string name;
int value1,value2;
bool operator<(const Person& p)const
{
if (value1 != p.value1)
return value1 > p.value1;
if (value2 != p.value2)
return value2 > p.value2;
return cmp(name, p.name);
}
}people[maxn];
int r1()
{
int sum = 0;
for (int i = 0; i < 5; i++)
sum += pai[i];
return sum;
}
int r2()
{
for (int i = 0; i < 4; i++)
{
if (pai[i] == pai[i + 1])
{
int sum = 0;
for (int j = 0; j < i; j++)
sum += pai[j];
for (int j = i + 2; j < 5; j++)
sum += pai[j];
return pai[i] * 42 + sum;
}
}
return 0;
}
int r3()
{
if (pai[0] == pai[1] && pai[3] == pai[4])
return pai[3] * 196 + pai[0] * 14 + pai[2];
if (pai[1] == pai[2] && pai[3] == pai[4])
return pai[3] * 196 + pai[1] * 14 + pai[0];
if (pai[0] == pai[1] && pai[2] == pai[3])
return pai[2] * 196 + pai[0] * 14 + pai[4];
return 0;
}
int r4()
{
if (pai[0] == pai[2])
return pai[2] * 28 + pai[3] + pai[4];
if (pai[1] == pai[3])
return pai[2] * 28 + pai[0] + pai[4];
if (pai[2] == pai[4])
return pai[2] * 28 + pai[0] + pai[1];
return 0;
}
int r5()
{
if (pai[0] == pai[1] && pai[2] == pai[4])
return pai[2] * 14 + pai[0];
if (pai[0] == pai[2] && pai[3] == pai[4])
return pai[2] * 14 + pai[3];
return 0;
}
int r6()
{
if (pai[0] == pai[3])
{
return pai[0] * 14 + pai[4];
}
if (pai[1] == pai[4])
{
return pai[1] * 14 + pai[0];
}
return 0;
}
int r7()
{
for (int i = 1; i < 5; i++)
{
if (pai[i] - pai[i - 1] != 1)
return 0;
}
return pai[4];
}
int r8()
{
if (pai[0] != 1)
return 0;
for (int i = 1; i < 5; i++)
if (pai[i] != 10 + i - 1)
return 0;
return 8;
}
vector<int(*)(void)> fun = { r1,r2,r3,r4,r5,r6,r7,r8 };
int find(const string& s)
{
int cont = 0;
for (int i = 0; i < s.size(); i++)
{
if (s.at(i) == '1' && s.at(i + 1) == '0')
{
pai[cont++] = 10;
i++;
}
else
pai[cont++] = tran.find(s.at(i))->second;
}
sort(pai, pai + 5);
int x;
for (int i = 7; i >= 0; i--)
{
x = fun[i]();
if (x)
{
value2 = x;
return i;
}
}
}
void put(const string& s1,const string& s2)
{
value1 = find(s2);//得到名次
people[tot].name = s1;
people[tot].value1 = value1;
people[tot].value2 = value2;
tot++;
}
int main()
{
cin >> n;
while (n--)
{
cin >> s1 >> s2;
put(s1, s2);//得到排名,并放进数组中
}
sort(people, people + tot);
for (int i = 0; i < tot; i++)
cout << people[i].name << endl;
return 0;
}
note
- 这个题目可以使用函数数组来测试所有规则。