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” |
Input
输入文件包含多组测试数据,第一行输入一个整数表示测试数据的组数 T ( T < = 20 ) T (T <= 20) T(T<=20);
每组测试数据的第一行输入一个整数表示该组测试数据的命令总数 Q ( Q < = 1 e 5 ) Q (Q <= 1e5) Q(Q<=1e5);
每组测试数据的 2 Q + 1 2 ~ Q+1 2 Q+1 行为具体的操作 (MKDIR、RM 操作总数不超过 5000 5000 5000);
面对数据范围你要思考的是他们代表的 “命令” 执行的最大可接受复杂度,只有这样你才能知道你需要设计的是怎样复杂度的系统。
Output
每组测试数据的输出结果间需要输出一行空行。注意大小写敏感。
时空限制
Time limit 6000 ms
Memory limit 1048576 kB
Sample 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
Sample 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
hint
英文原版题面:
思路做法:
按照题目要求将每个功能逐个写成函数实现或整体封装成一个类,但是要先有一个整体上的把握,不然写到后面才会发现前面的代码需要额外地增加一些功能以支持后面的函数实现。在这道题目中,用数组存储了多叉树,结点只需记录父结点和孩子结点相应的id即可,创建目录注意更新sz是用了一种递推的方法一直更新到root,删除同理,但是删除的目录sz可能不为1,因此小心思维固化。进入目录要分2种情况考虑,是到上层目录还是下层目录,这3种操作做完后都要记录his历史记录,undo撤销操作取出最近的一次操作,并执行反操作即可。
几种询问操作主要考虑时间限制,以空间换时间,包括sz记录了目录的大小,每次更新了目录都更新sz,这样遇到sz操作直接输出即可。ls操作相对简单,tree操作用到了懒更新,防止超时,即每次要查询时先判断当前存储的答案是不是更新后的,如果不是就更新答案,否则可以直接按所要求的的格式输出。
总结:
难点在于tree操作,首先是用到了懒更新,要记录tag值,另外注意递归实现最后5个子目录的查询里目录的顺序。
代码:
#include <iostream>
#include <string>
#include <map>
#include <vector>
using namespace std;
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
const int N = 5e3+50;
struct Dir{
string nm;
int pre; // 上层目录
map<string, int> child; // 子目录
int sz; // 子目录的数目 +1
vector<string> p5, b5; // 存储前5个和后5个
bool tag;
Dir(){}
Dir(string _nm, int _pre):nm(_nm), pre(_pre), sz(1), tag(false){
child.clear(); p5.clear(); b5.clear();
}
}dir[N];
struct His{
string cmd;
string nm;
int d;
His(){}
His(string _cmd, string _nm, int _d):cmd(_cmd), nm(_nm), d(_d){}
};
int now, cnt;
vector<His> his; // 历史记录
void update(int num){
int temp = now;
while(temp != -1){
dir[temp].tag = false;
dir[temp].sz += num;
temp = dir[temp].pre;
}
}
void mkdir(string s){
if(dir[now].child.find(s) != dir[now].child.end()){
cout << "ERR" << endl; return;
} // 找到就不能创建
dir[cnt] = Dir(s, now); // 创建新目录
cout << "OK" << endl; his.push_back(His("mkdir", s, cnt));
dir[now].child[s] = cnt++; update(1);
}
void rm(string s){
if(dir[now].child.find(s) == dir[now].child.end()){
cout << "ERR" << endl; return;
} // 没找到
int num = dir[now].child.find(s)->second;
cout << "OK" <<endl; his.push_back(His("rm", s, num));
dir[now].child.erase(s); update(-1*dir[num].sz);
}
void cd(string s){
if(s == ".."){
int pre = dir[now].pre;
if(pre != -1){
cout << "OK\n"; his.push_back(His("cd", s, now));
now = dir[now].pre; return;
}else{
cout << "ERR\n"; return;
}
}
if(dir[now].child.find(s) == dir[now].child.end()){
cout << "ERR\n"; return;
} // 没找到
cout << "OK\n"; his.push_back(His("cd", s, now));
now = dir[now].child.find(s)->second;
}
void sz(int d){ // =1代表只有自己,没有下层目录
cout << dir[d].sz << endl;
}
void ls(int d){
int len = dir[d].child.size();
if(len == 0){
cout << "EMPTY\n";
}else if(len <= 10){
for(map<string, int>::iterator it = dir[d].child.begin(); it != dir[d].child.end(); it++){
cout << dir[it->second].nm << endl;
}
}else{
map<string, int>::iterator it = dir[d].child.begin();
for(int i = 0; i < 5; ++i){
cout << dir[it->second].nm << endl; it++;
}
cout << "...\n";
it = dir[d].child.end();
for(int i = 0; i < 5; ++i) it--;
for(int i = 0; i < 5; ++i){
cout << dir[it->second].nm << endl; it++;
}
}
}
void pretrack(int d, vector<string>& v){
v.push_back(dir[d].nm);
int len = dir[d].child.size();
for(map<string, int>::iterator it = dir[d].child.begin(); it != dir[d].child.end(); it++){
pretrack(it->second, v); // 前序遍历
}
}
void pushdown(int d){
dir[d].p5.clear(); dir[d].b5.clear();
pretrack(d, dir[d].p5); // 正着找
dir[d].tag = true;
}
void treefirst(int num, int d, vector<string>& v){
v.push_back(dir[d].nm);
if(--num == 0) return;
int len = dir[d].child.size();
for(map<string, int>::iterator it = dir[d].child.begin(); it != dir[d].child.end(); it++){
int csz = dir[it->second].sz;
if(csz >= num){
treefirst(num, it->second, v); return;
}else{
treefirst(csz, it->second, v); num -= csz;
}
}
}
void treelast(int num, int d, vector<string>& v){
int len = dir[d].child.size();
map<string, int>::iterator it = dir[d].child.end();
while(len--){
it--;
int csz = dir[it->second].sz;
if(csz >= num){
treelast(num, it->second, v); return;
}else{
treelast(csz, it->second, v); num -= csz;
}
}
v.push_back(dir[d].nm);
}
void tree(int d){
if(dir[d].sz == 1){
cout << "EMPTY" << endl;
}else if(dir[d].sz > 1 && dir[d].sz <= 10){
if(!dir[d].tag){ // 没有更新答案,需要遍历
pushdown(d); // 把所有的都放进去
}
for(int i = 0, len = dir[d].p5.size(); i < len; ++i){
cout << dir[d].p5[i] << endl;
}
}else if(dir[d].sz > 10){
if(!dir[d].tag){ // 没有更新答案,需要遍历
dir[d].p5.clear(); dir[d].b5.clear();
treefirst(5, d, dir[d].p5);
treelast(5, d, dir[d].b5);
dir[d].tag = true;
}
for(int i = 0; i < 5; ++i){
cout << dir[d].p5[i] << endl;
}
cout << "...\n";
for(int i = 4; i >= 0; --i){
cout << dir[d].b5[i] << endl;
}
}
}
void undo(){
int len = his.size();
if(len == 0){
cout << "ERR" << endl; return;
}
string cmd = his[len-1].cmd;
cout << "OK" << endl; his.pop_back();
if(cmd == "mkdir"){
int goal = his[len-1].d;
string s = his[len-1].nm;
// dir[now] rm s
dir[now].child.erase(s);
update(-1*dir[goal].sz);
}else if(cmd == "rm"){
int goal = his[len-1].d;
string s = his[len-1].nm;
// dir[now] mkdir {s, goal}
dir[now].child[s] = goal;
update(1*dir[goal].sz);
}else if(cmd == "cd"){
now = his[len-1].d;
}
}
int main(){
__;
int t; cin >> t;
while(t--){
int q; cin >> q;
now = 0, cnt = 1;
his.clear();
dir[now] = Dir("root", -1);
for(int i = 0; i < q; ++i){
string cmd; cin >> cmd;
if(cmd == "MKDIR"){
string s; cin >> s;
mkdir(s);
// 在dir[cnt]中创建目录 上层目录为now
}else if(cmd == "RM"){
string s; cin >> s;
rm(s);
// 在dir[now]的子目录中找s 找到删除
}else if(cmd == "CD"){
string s; cin >> s;
cd(s);
// 在dir[now]的子目录中找s 或返回上层目录
}else if(cmd == "SZ"){
sz(now);
// 输出当前目录的大小
}else if(cmd == "LS"){
ls(now);
//查询子目录
}else if(cmd == "TREE"){
tree(now);
// 查询后代
}else if(cmd == "UNDO"){
undo();
// 撤销
}
}
}
return 0;
}
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 张牌不同,其值不同。下面依次列举了这手牌的形成规则:
1.大牌:这手牌不符合下面任一个形成规则。如果 α 和 β 都是大牌,那么定义它们的大小为组成这手牌的 5 张牌的大小总和。
2.对子:5 张牌中有 2 张牌的值相等。如果 α 和 β 都是对子,比较这个 “对子” 的大小,如果 α 和 β 的 “对子” 大小相等,那么比较剩下 3 张牌的总和。
3.两对:5 张牌中有两个不同的对子。如果 α 和 β 都是两对,先比较双方较大的那个对子,如果相等,再比较双方较小的那个对子,如果还相等,只能比较 5 张牌中的最后那张牌组不成对子的牌。
4.三个:5 张牌中有 3 张牌的值相等。如果 α 和 β 都是 “三个”,比较这个 “三个” 的大小,如果 α 和 β 的 “三个” 大小相等,那么比较剩下 2 张牌的总和。
5.三带二:5 张牌中有 3 张牌的值相等,另外 2 张牌值也相等。如果 α 和 β 都是 “三带二”,先比较它们的 “三个” 的大小,如果相等,再比较 “对子” 的大小。
6.炸弹:5 张牌中有 4 张牌的值相等。如果 α 和 β 都是 “炸弹”,比较 “炸弹” 的大小,如果相等,比较剩下那张牌的大小。
7.顺子:5 张牌中形成 x, x+1, x+2, x+3, x+4。如果 α 和 β 都是 “顺子”,直接比较两个顺子的最大值。
8.龙顺:5 张牌分别为 10、J、Q、K、A。
作为一个称职的魔法师,东东得知了全场人手里 5 张牌的情况。他现在要输出一个排行榜。排行榜按照选手们的 “一手牌” 大小进行排序,如果两个选手的牌相等,那么人名字典序小的排在前面。
不料,此时一束宇宙射线扫过,为了躲避宇宙射线,东东慌乱中清空了他脑中的 Cache。请你告诉东东,全场人的排名
Input
输入包含多组数据。每组输入开头一个整数
n
(
1
<
=
n
<
=
1
e
5
)
n (1 <= n <= 1e5)
n(1<=n<=1e5),表明全场共多少人。
随后是
n
n
n 行,每行一个字符串
s
1
s1
s1 和
s
2
(
1
<
=
∣
s
1
∣
,
∣
s
2
∣
<
=
10
)
,
s
1
s2 (1 <= |s1|,|s2| <= 10), s1
s2(1<=∣s1∣,∣s2∣<=10),s1 是对应人的名字,
s
2
s2
s2 是他手里的牌情况。
Output
对于每组测试数据,输出 n n n 行,即这次全场人的排名。
Sample Input
3
DongDong AAA109
ZJM 678910
Hrz 678910
Sample Output
Hrz
ZJM
DongDong
思路做法:
对于任一玩家的手牌,首先要从字符串形式转化为5个数字的形式。
然后计算这5张手牌所属的类别或它的值,但是不能仅考虑其值,因为若2副牌值相同,还有一系列其他的用于比较大小的值,这里使用v记录值,value数组存储所有其他可能需要用来比较大小的值。根据题目要求重载Player类的小于号并sort排序即可得到答案。
总结:
思路很清晰,可能就是判断牌的值时要写大量的代码(我也不知道有没有很简单就可以判定的),坚持写完其实不难
代码:
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
const int N = 1e5+50;
int n;
struct Player{
string s1; // 名字
string s2; // 牌
int pai[5]; // 转化后的牌
vector<int> value; // 一系列比较的值
int v;
void trans(){ // 将手牌字符串转化为整形数组
int len = s2.length(), p = 0;
for(int i = 0; i < len; ++i){
if(i < len-1 && s2[i+1] == '0'){
pai[p++] = 10; i++; //跳过0
}else if(s2[i] == 'A') pai[p++] = 1;
else if(s2[i] == 'J') pai[p++] = 11;
else if(s2[i] == 'Q') pai[p++] = 12;
else if(s2[i] == 'K') pai[p++] = 13;
else pai[p++] = s2[i] - '0';
}
}
void getValue(){ // 计算手牌值 返回手牌属于哪一种情况
sort(pai, pai + 5);
bool r[9]; // 1~8 8种情况
map<int, int> mp; // 牌->数目
for(int i = 1; i <= 8; ++i) r[i] = true;
for(int i = 0; i < 5; ++i){
if(i == 0 && pai[i] != 1) r[8] = false; // 排序后首位是A
if(i == 1 && pai[i] != 10) r[8] = false;
if(i > 1 && pai[i] != pai[i-1] + 1) r[8] = false;
if(i > 0 && pai[i] != pai[i-1] + 1) r[7] = false;
mp[pai[i]]++;
}
int val[5], cnt = 0;
for(map<int,int>::iterator it = mp.begin(); it != mp.end(); it++){
val[cnt++] = it->second;
}
sort(val, val+cnt, greater<int>()); // 降序排序
for(int i = 0; i < cnt; ++i){
if(i == 0 && val[i] < 4) r[6] = false;
if(i == 0 && val[i] < 3){
r[5] = false; r[4] = false;
}
if(i > 0 && val[i] < 2) r[5] = false;
if(i == 0 && val[i] < 2){
r[3] = false; r[2] = false;
}
if(i == 1 && val[i] < 2) r[3] = false;
}
int ans = 0;
for(int i = 8; i >= 1; --i){
if(r[i]){
ans = i; break;
}
}
if(ans == 7){
value.push_back(pai[4]); // 顺子的最大值
}else if(ans == 6){
for(map<int,int>::iterator it = mp.begin(); it != mp.end(); it++){
if(it->second == 4) value.push_back(it->first);
}
for(map<int,int>::iterator it = mp.begin(); it != mp.end(); it++){
if(it->second == 1) value.push_back(it->first);
}
}else if(ans == 5){
for(map<int,int>::iterator it = mp.begin(); it != mp.end(); it++){
if(it->second == 3) value.push_back(it->first);
}
for(map<int,int>::iterator it = mp.begin(); it != mp.end(); it++){
if(it->second == 2) value.push_back(it->first);
}
}else if(ans == 4){
int sum = 0;
for(map<int,int>::iterator it = mp.begin(); it != mp.end(); it++){
if(it->second == 3) value.push_back(it->first);
if(it->second == 1) sum += it->first;
}value.push_back(sum);
}else if(ans == 3){
for(map<int,int>::iterator it = mp.begin(); it != mp.end(); it++){
if(it->second == 2) value.push_back(it->first);
}
sort(value.begin(), value.end(), greater<int>());
for(map<int,int>::iterator it = mp.begin(); it != mp.end(); it++){
if(it->second == 1) value.push_back(it->first);
}
}else if(ans == 2){
int sum = 0;
for(map<int,int>::iterator it = mp.begin(); it != mp.end(); it++){
if(it->second == 2) value.push_back(it->first);
if(it->second == 1) sum += it->first;
}value.push_back(sum);
}else if(ans == 1){
int sum = 0;
for(int i = 0; i < 5; ++i) sum += pai[i];
value.push_back(sum);
}
v = ans;
}
void show(){
cout << s1 <<" "<< s2 << endl;
}
bool operator<(const Player& p) const {
if(v != p.v) return v > p.v;
for(int i = 0, len = value.size(); i < len; ++i){
if(value[i] != p.value[i]) return value[i] > p.value[i];
}
return s1 < p.s1;
}
}player[N];
int main(){
while(cin >> n){
for(int i = 0; i < n; ++i){
cin >> player[i].s1;
cin >> player[i].s2;
player[i].trans(); player[i].getValue();
}
sort(player, player + n);
for(int i = 0; i < n; ++i){
cout << player[i].s1 << endl;
}
}
return 0;
}
C - 签到题,独立思考哈
题意:
SDUQD 旁边的滨海公园有 x x x 条长凳。第 i i i 个长凳上坐着 a i a_i ai 个人。这时候又有 y y y 个人将来到公园,他们将选择坐在某些公园中的长凳上,那么当这 y y y 个人坐下后,记 k = k = k= 所有椅子上的人数的最大值,那么 k k k可能的最大值 m x mx mx和最小值 m n mn mn分别是多少。
Input
第一行包含一个整数
x
(
1
<
=
x
<
=
100
)
x (1 <= x <= 100)
x(1<=x<=100) 表示公园中长椅的数目
第二行包含一个整数
y
(
1
<
=
y
<
=
1000
)
y (1 <= y <= 1000)
y(1<=y<=1000) 表示有 y 个人来到公园
接下来
x
x
x 个整数
a
i
(
1
<
=
a
i
<
=
100
)
a_i (1<=a_i<=100)
ai(1<=ai<=100),表示初始时公园长椅上坐着的人数
Output
输出 m n mn mn 和 m x mx mx
Sample Input
3
7
1
6
1
Sample Output
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
思路做法:
最大值很容易想到,在原本的数组里找到最大的
m
a
x
(
a
i
)
max(a_i)
max(ai)再加上后来来的人数
y
y
y即可。
接下来求最小值,首先将原数从小到大排序,然后从最小的数开始,从后来人数
y
y
y里分出数来使它和后面的数相等,若一直到最后
y
y
y仍大于0,说明原本数组的最大值可以更大,则
m
n
=
a
[
x
−
1
]
+
y
/
x
+
(
y
%
x
?
1
:
0
)
mn = a[x-1]+y/x+(y\%x?1:0)
mn=a[x−1]+y/x+(y%x?1:0)否则,
y
y
y不足以将
a
[
x
−
1
]
a[x-1]
a[x−1]更新,
m
n
=
a
[
x
−
1
]
mn = a[x-1]
mn=a[x−1]
总结:
想到思路后代码并不难写
代码:
#include <stdio.h>
#include <algorithm>
using namespace std;
const int N = 100+50;
int a[N];
int main(){
int x, y; scanf("%d%d", &x, &y);
int mx = 0, mn, len = 1, isBreak = 0;
for(int i = 0; i < x; ++i){
scanf("%d", &a[i]);
if(a[i] > mx) mx = a[i];
}
mx += y;
sort(a, a+x);
for(int i = 1; i < x; ++i){
int h = a[i] - a[i-1];
y -= h * (len++);
if(y < 0){
isBreak = 1; break;
}
}
mn = isBreak ? a[x-1] : a[x-1]+y/x+(y%x?1:0);
printf("%d %d\n", mn, mx);
return 0;
}