**
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
解题思路
目录管理器的存储为树形结构,每一个目录为一个节点;样例的输入为命令,命令中包括要执行的操作和操作对应的参数信息,且在操作过程中需要对操作成功的命令进行记录。所以需要对目录和命令进行封装。
输入字符串为命令,对命令类型进行识别,定义目录节点调用相应的函数,这些函数是对目录也就是树的操作。
对于每一个目录节点,需要保存的内容有当前目录节点的名称、该目录的子目录和上级目录、当前目录的子树大小、当前节点的十个后代。
各个函数的实现具体见代码注释。
提交代码出现的问题
RunTime Error,怎么改都不对,结果将ls函数中对子目录大小的判断由num<=10&&num>=1改为num<=10就对了(具体原因我也不知道。。。)
C++代码
#include<iostream>
#include<string>
#include<map>
#include<vector>
using namespace std;
char tmps[30];
struct directory//目录结构体 目录名称、子目录、上级目录、子树大小
{
string name; //目录名称
map<string,directory*> child;//子目录
directory *parent; //上级目录
int size; //子树大小
vector<string> tendes;//保存当前节点的十个后代
bool updated; //记录当前节点的子树有无变动,无变动则十个后代无需更新
directory(string name,directory *parent)//目录构造函数
{
this->name=name;
this->parent=parent;
this->size=1;
}
directory* mkdir(string name)//创建目录
{
if(child.find(name)!=child.end()) return nullptr;//名为name的子目录已经存在
directory* ch=new directory(name,this);//创建新目录,ch为指向新目录的指针
child[name]=ch; //将新目录存为当前目录的子目录
maintain(+1); //向上维护各节点的子树大小
return ch; //返回指向新建子目录的指针
}
directory* rm(string name)//删除目录
{
auto it=child.find(name);
if(it==child.end()) return nullptr;//要删除的目录不存在
maintain(-1*it->second->size);
child.erase(it);
return it->second;//返回指向成功删除子目录的指针
}
directory* cd(string name)//进入目录
{
if(name=="..") return this->parent;
return getchild(name);
}
directory* getchild(string name)//根据子目录的名字取出子目录
{
auto it=child.find(name);
if(it==child.end()) return nullptr;//返回空指针
return it->second;//返回指向这个孩子的指针
}
void sz()//输出当前目录大小
{
cout<<this->size<<endl;
}
void ls()//输出子目录名
{
int num=child.size();
if(num==0) cout<<"EMPTY"<<endl;
else if(num<=10)
{
for(auto it=child.begin();it!=child.end();it++) cout<<it->first<<endl;
}
else
{
auto it=child.begin();
for(int i=0;i<5;i++,it++) cout<<it->first<<endl;
cout<<"..."<<endl;
it=child.end();
for(int i=0;i<5;i++) it--;
for(int i=0;i<5;i++,it++) cout<<it->first<<endl;
}
}
void tree()//输出以当前目录为根的子树前序遍历结果
{
if(size==1) cout<<"EMPTY"<<endl;
else if(size<=10) //后代目录数小于10,全部输出
{
if(this->updated)//当前节点的子树有变动,更新后代
{
tendes.clear();
treeall(&tendes);
this->updated=false;
}
for(int i=0;i<size;i++) cout<<tendes.at(i)<<endl;
}
else //后代目录数大于10
{
if(this->updated)
{
tendes.clear();
treef(5,&tendes);
treel(5,&tendes);
this->updated=false;
}
for(int i=0;i<5;i++) cout<<tendes.at(i)<<endl;
cout<<"..."<<endl;
for(int i=9;i>=5;i--) cout<<tendes.at(i)<<endl;
}
}
bool addchild(directory *ch)//添加子目录
{
if(child.find(ch->name)!=child.end()) return false;//要加入的子目录已经存在
child[ch->name]=ch;
maintain(+ch->size);
return true;
}
void maintain(int delta)//向上维护子树大小
{
updated=true;
size=size+delta;
if(parent!=nullptr) parent->maintain(delta);
}
private:
void treeall(vector<string>* bar)//更新全桶
{
bar->push_back(name);
for(auto it=child.begin();it!=child.end();it++)
{
it->second->treeall(bar);
}
}
void treef(int num,vector<string>* bar) //更新前序几个
{
bar->push_back(name);
if(--num==0) return;
int n=child.size();
auto it=child.begin();
for(int i=0;i<n;i++,it++)
{
int ss=it->second->size;
if(ss>=num)
{
it->second->treef(num,bar);
return;
}
else
{
it->second->treef(ss,bar);
num=num-ss;
}
}
}
void treel(int num,vector<string>* bar)//更新后序几个
{
int n=child.size();
auto it=child.end();
for(int i=0;i<n;i++)
{
it--;
int ss=it->second->size;
if(ss>=num)
{
it->second->treel(num,bar);
return;
}
else
{
it->second->treel(ss,bar);
num=num-ss;
}
}
bar->push_back(name);
}
};
struct command//命令结构体 命令类型、参数、刚刚操作的目录节点
{
const string c[7]={"MKDIR","RM","CD","SZ","LS","TREE","UNDO"};
int type;
string arg;
directory *tmpdir;//刚刚操作的目标节点
command(string s)//命令构造函数
{
for(int i=0;i<7;i++)
{
if(s==c[i])
{
type=i;
if(i<3)//操作类型的命令有参数
{
cin>>tmps;
arg=tmps;
}
return;
}
}
}
};
void solve()
{
int q=0;
cin>>q;
directory *now=new directory("root",nullptr);
vector<command*> cmdlist;//存放刚刚成功执行的命令
for(int i=0;i<q;i++)
{
cin>>tmps;
command *cmd=new command(tmps);
switch(cmd->type)
{
case 0://mkdir
{
cmd->tmpdir=now->mkdir(cmd->arg);
if(cmd->tmpdir==nullptr) cout<<"ERR"<<endl;
else
{
cout<<"OK"<<endl;
cmdlist.push_back(cmd);
}
break;
}
case 1://rm
{
cmd->tmpdir=now->rm(cmd->arg);
if(cmd->tmpdir==nullptr) cout<<"ERR"<<endl;
else
{
cout<<"OK"<<endl;
cmdlist.push_back(cmd);
}
break;
}
case 2://cd
{
directory *ch=now->cd(cmd->arg);
if(ch==nullptr) cout<<"ERR"<<endl;
else
{
cout<<"OK"<<endl;
cmd->tmpdir=now;
now=ch;
cmdlist.push_back(cmd);
}
break;
}
case 3://sz
now->sz();
break;
case 4://ls
now->ls();
break;
case 5://tree
now->tree();
break;
case 6://undo
{
bool success=false;//标记是否撤销成功
while(!success&&!cmdlist.empty())
{
cmd=cmdlist.back();//取出上一个成功执行的命令
cmdlist.pop_back();
switch(cmd->type)//判断命令类型,只有前三个操作需要撤销
{
case 0://mkdir
success=now->rm(cmd->arg)!=nullptr;//删除创建成功的目录
break;
case 1://rm
success=now->addchild(cmd->tmpdir);//将删除成功的目录重新添加
break;
case 2://cd
now=cmd->tmpdir;//回到刚刚操作的节点
success=true;
break;
}
}
if(success) cout<<"OK"<<endl;
else cout<<"ERR"<<endl;
break;
}
}
}
}
int main()
{
int t=0;
cin>>t;
for(int i=0;i<t;i++)
{
solve();
}
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。请你告诉东东,全场人的排名
输入
输入包含多组数据。每组输入开头一个整数 n (1 <= n <= 1e5),表明全场共多少人。
随后是 n 行,每行一个字符串 s1 和 s2 (1 <= |s1|,|s2| <= 10), s1 是对应人的名字,s2 是他手里的牌情况。
输出
对于每组测试数据,输出 n 行,即这次全场人的排名。
样例输入
3
DongDong AAA109
ZJM 678910
Hrz 678910
样例输出
Hrz
ZJM
DongDong
解题思路
对于八种类型的牌,判断某一种类型的大小,最多需要进行四次比较(包括名字字典序的比较),那么,可以定义一个结构体,结构体中包括牌的类型、持牌人的名字、以及三个判断系数。重载小于号,优先比较牌的类型,其次是ju1、ju2、ju3三个判断系数,最后根据姓名的字典序比较。当判断出每一种牌的类型后,计算出相应类型比较大小的各个数据,然后定义一个结构体,将相应的数据赋值给结构体,将结构体压入一个最大堆。
最终循环从最大堆中取出结构体,输出结构体对应的姓名即可。
对于牌类型的判断:
定义set:s1、s2、s3、s4分别记录五张牌中出现一、二、三、四次的牌有哪些,set可以自动由小到大排序,取最小或最大可以直接从首尾取值,方便判断赋值。
出现的错误
注意set的end()返回的是容器里的元素个数,不是最大元素,end()的上一个是最大元素,可以定义迭代器求出。最初使用end()求最大值的时候出现了错误。
C++代码
#include<iostream>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<set>
using namespace std;
struct node
{
int type;
string name;
int ju1,ju2,ju3;
void init()
{
type=0;
ju1=0,ju2=0,ju3=0;
}
bool operator<(const node &p) const
{
if(type!=p.type) return type<p.type;
else if(ju1!=p.ju1) return ju1<p.ju1;
else if(ju2!=p.ju2) return ju2<p.ju2;
else if(ju3!=p.ju3) return ju3<p.ju3;
else return name.compare(p.name)>0;
}
};
map<char,int> mp;
priority_queue<node> q;
int vis[15];
void judge(string &name,string &card)
{
memset(vis,0,sizeof(vis));
//将字符串转化为整数存储起来
for(int i=0;i<card.length();i++)
{
if(card[i]=='0') continue;
if(card[i]=='1')
{
vis[10]++;
continue;
}
if(card[i]<=57&&card[i]>=50)
{
int index=card[i]-48;
vis[index]++;
}
else if(card[i]<=90&&card[i]>=65)
{
vis[mp[card[i]]]++;
}
}
//判断每种牌出现的次数
set<int> s1,s2,s3,s4;
for(int i=0;i<=13;i++)
{
if(vis[i]==0) continue;
else if(vis[i]==1) s1.insert(i);
else if(vis[i]==2) s2.insert(i);
else if(vis[i]==3) s3.insert(i);
else if(vis[i]==4) s4.insert(i);
}
//对这一手牌进行分类
node nd;
nd.init();
if(s2.size()==1&&s1.size()==3)//2 对子
{
int sum=0;
set<int>::iterator it;
for(it=s1.begin();it!=s1.end();it++)
{
sum=sum+*it;
}
nd.type=2,nd.ju1=*(s2.begin()),nd.ju2=sum,nd.name=name;
q.push(nd);
}
else if(s2.size()==2)//3 两对
{
set<int>::iterator it=s2.begin();
nd.ju2=*it;
it++;
nd.ju1=*it;
nd.type=3,nd.ju3=*(s1.begin()),nd.name=name;
q.push(nd);
}
else if(s3.size()==1&&s1.size()==2)//4 三个
{
int sum=0;
set<int>::iterator it;
for(it=s1.begin();it!=s1.end();it++)
{
sum=sum+*it;
}
nd.type=4,nd.ju1=*(s3.begin()),nd.ju2=sum,nd.name=name;
q.push(nd);
}
else if(s3.size()==1&&s2.size()==1)//5 三带二
{
nd.type=5,nd.ju1=*(s3.begin()),nd.ju2=*(s2.begin()),nd.name=name;
q.push(nd);
}
else if(s4.size()==1)//6 炸弹
{
nd.type=6,nd.ju1=*(s4.begin()),nd.ju2=*(s1.begin()),nd.name=name;
q.push(nd);
}
else if(s1.size()==5)
{
set<int>::iterator it;
set<int>::iterator pre;
for(it=s1.begin();it!=s1.end();it++)
{
pre=it;
}
if(vis[1]==1&&vis[10]==1&&vis[11]==1&&vis[12]==1&&vis[13]==1)//8 龙顺
{
nd.type=8,nd.name=name;
q.push(nd);
}
else if(*pre-*(s1.begin())==4)//7 顺子
{
nd.type=7,nd.ju1=*pre,nd.name=name;
q.push(nd);
}
else//1 大牌
{
int sum=0;
set<int>::iterator it;
for(it=s1.begin();it!=s1.end();it++)
{
sum=sum+*it;
}
nd.type=1,nd.ju1=sum,nd.name=name;
q.push(nd);
}
}
s1.clear();
s2.clear();
s3.clear();
s4.clear();
}
int main()
{
mp['A']=1,mp['J']=11,mp['Q']=12,mp['K']=13;
int n=0;
while(cin>>n)
{
for(int i=0;i<n;i++)
{
string name,card;
cin>>name>>card;
judge(name,card);
}
while(!q.empty())
{
cout<<(q.top()).name<<endl;
q.pop();
}
}
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
解题思路
k可能的最大值为a_i最大值加上y
k可能的最小值:将y填补到a_i中,使得每一个长凳的a_i都等于其初始的最大值,再将剩下的y 平均到每一个长凳上,最后的最大值就是k可能的最小值;如果y填补不完就减到0,那么最小值就是初始a_i的最大值。
C++代码
#include<iostream>
using namespace std;
int main()
{
int x=0,y=0,maxx=0;
cin>>x>>y;
int a[x+1];
for(int i=0;i<x;i++)
{
cin>>a[i];
if(a[i]>maxx) maxx=a[i];
}
int yy=y;
for(int i=0;i<x;i++)
{
if(a[i]<maxx&&a[i]+yy>maxx) yy=a[i]+yy-maxx;
else if(a[i]<maxx&&a[i]+yy<=maxx)
{
yy=0;
break;
}
}
int mn=0;
if(yy==0) mn=maxx;
else mn=maxx+(yy-1)/x+1;
cout<<mn<<" "<<maxx+y<<endl;
return 0;
}