A.目录管理
题目描述
咕咕东的雪梨电脑的操作系统在上个月受到宇宙射线的影响,时不时发生故障,他受不了了,想要写一个高效易用零bug的操作系统 —— 这工程量太大了,所以他定了一个小目标,从实现一个目录管理器开始。前些日子,东东的电脑终于因为过度收到宇宙射线的影响而宕机,无法写代码。他的好友TT正忙着在B站看猫片,另一位好友瑞神正忙着打守望先锋。现在只有你能帮助东东!初始时,咕咕东的硬盘是空的,命令行的当前目录为根目录 root。目录管理器可以理解为要维护一棵有根树结构,每个目录的儿子必须保持字典序。
输入:
输入文件包含多组测试数据,第一行输入一个整数表示测试数据的组数 T (T <= 20);
每组测试数据的第一行输入一个整数表示该组测试数据的命令总数 Q (Q <= 1e5);
每组测试数据的 2 ~ Q+1 行为具体的操作 (MKDIR、RM 操作总数不超过 5000);
面对数据范围你要思考的是他们代表的 “命令” 执行的最大可接受复杂度,只有这样你才能知道你需要设计的是怎样复杂度的系统。
输出:
每组测试数据的输出结果间需要输出一行空行。注意大小写敏感。
时空限制:
Time limit 6000 ms
Memory limit 1048576 kB
思路分析
这道题是跟着助教的讲解走的,后期写代码也是参照PPT写出来的,这类型题目很有难度,但是写下来发现只要有清晰的思路,有着清晰的设计安排,就会容易得多。
正如助教的讲解所说,不知道如何入手,就先拟定框架,再一步一步完善。题目中主要涉及两个对象:指令和文件。对于指令可以定义int型成员type表示所执行的指令是哪一种,方便主函数中对操作进行switch case,定义string类型arg表示指令的具体操作,定义文件指针类型tmpdir记录当前对文件进行操作的节点,根据其值是否为空判断指令是否成功执行。在dira结构体中,定义数据成员有string类型的name表示文件名,定义map类型的children表示文件的孩子。定义指针par表示父亲方便执行CD,定义dirasize表示当前目录的大小方便执行SZ,定义vector类型的tendescendants保存当前的10个后代,方便执行LS。定义updated表示是否之前已经有遍历操作,如果执行LS操作时,updated为真就要先把tendescendants清空,再来重新存储新的一轮遍历。定义公有函数getchild取出子目录并且返回这个节点。定义mkdir函数创建子目录即在children数组末尾加上新节点即可并且返回这个创建的节点。定义rm函数找到要删除的节点将其删除并且返回这个被删除的节点。另外还定义cd、addchild、maintain、sz、ls、tree等函数。这里只是想要记录一下做这道题的那种思维方向,重点并不是函数细节。函数细节在代码中已经很详细了。
代码实现
#include<bits/stdc++.h>
using namespace std;
char tmps[20];//记录当前操作
struct dira{
string name;//当前的目录名
map<string,dira*> children;//子目录
dira *par;//上一级目录,便于执行CD
int dirasize;//当前目录的大小即1+孩子个数
bool updated;
vector<string> tendescendants;//保存当前的10个后代(或者前5个和后5个)
dira(string thename,dira* thepar){
name=thename;
par=thepar;
dirasize=1;//目前只有一个父节点
}
public:
dira* getchild(string s);
dira* mkdir(string s);
dira* rm(string s);
dira* cd(string s);
bool addchild(dira* ch);
void maintain(int delta);//向上维护子树大小
void sz();
void ls();
void tree();
void clear();
private:
void treeall(vector<string>& bar);//全部后代插入桶
void treefirstsome(int num,vector<string> &bar);//前序遍历并加入首要的num个 后代
void treelastsome(int num,vector<string> &bar);//后序遍历并且加入首要的num个后代
//既然tendscendants是vector数组类型,那就不要忘记这些函数中的参数要加引符号
};
struct command{
const string cmdnames[7]={"MKDIR","RM","CD","SZ","LS","TREE","UNDO"};
int type;//命令类型
string arg;// 命令的参数
dira* tmpdir;//记录上一个操作的目的节点
command(string s){
for(int i=0;i<7;i++)
if(cmdnames[i]==s){
type=i;
if(i<3) {scanf("%s",tmps);arg=tmps;}//MKDIR,RM,CD操作之后有这些参数
return;
}
}
};
list<command*> cmdlist;
dira* dira::getchild(string s){
//取子目录并返回,不存在返回空指针
map<string,dira*>::iterator it=children.find(s);
if(it==children.end()) return NULL;
return it->second;
}
dira* dira::mkdir(string s){
//创建子目录并返回,创建失败则返回空指针
if(children.find(s)!=children.end()) return NULL;
dira* ch=new dira(s,this);
children[s]=ch;
//tmpdir=ch;
maintain(+1);
return ch;
}
dira* dira::rm(string s){
auto it=children.find(s);
if(it==children.end()) return NULL;
maintain(-1*it->second->dirasize);
// tmpdir=it.second;
children.erase(it);
return it->second;
}
dira* dira::cd(string s){
if(".."==s) return this->par;
return getchild(s);
}
bool dira::addchild(dira*ch){
//加入子目录并返回成功与否
if(children.find(ch->name)!=children.end()) return false;
children[ch->name]=ch;
maintain(+ch->dirasize);
return true;
}
void dira::maintain(int delta){
//向上维护子树大小
updated=true;//已经做了修改
dirasize+=delta;
if(par!=NULL)
par->maintain(delta);
}
void dira::sz(){
printf("%d\n",this->dirasize);
}
void dira::ls(){
int sz=children.size();
if(sz==0) printf("EMPTY\n");
else if(sz<=10) {
for(auto&entry:children)
printf("%s\n",entry.first.c_str());
}
else{
map<string,dira*>::iterator it=children.begin();
for(int i=0;i<5;i++,it++) printf("%s\n",it->first.c_str());
printf("...\n");
it=children.end();
for(int i=0;i<5;i++) it--;
for(int i=0;i<5;i++,it++) printf("%s\n",it->first.c_str());
}
}
void dira::tree(){
if(dirasize==1) printf("EMPTY\n");
else if(dirasize<=10){
if(this->updated){
tendescendants.clear();
treeall(tendescendants);
this->updated=false;
}
for(int i=0;i<dirasize;i++){
printf("%s\n",tendescendants.at(i).c_str());
}
}
else{
if(this->updated){
tendescendants.clear();
//tendescendants=NULL;
treefirstsome(5,tendescendants);
treelastsome(5,tendescendants);
this->updated=false;
}
for(int i=0;i<5;i++) printf("%s\n",tendescendants.at(i).c_str());
printf("...\n");
for(int i=9;i>=5;i--) printf("%s\n",tendescendants.at(i).c_str());
}
}
void dira::treeall(vector<string>& bar){
//更新全桶
bar.push_back(name);//???????
for(auto& entry:children) entry.second->treeall(bar);
}
void dira::treefirstsome(int num,vector<string>& bar){
//更新前序几个
bar.push_back(name);/???????????????
if(--num==0) return;
int n=children.size();
auto it=children.begin();
while(n--){
int sts=it->second->dirasize;
if(sts>=num){
it->second->treefirstsome(num,bar);
return;
}
else{
it->second->treefirstsome(sts,bar);
num-=sts;
}
it++;
}
}
void dira::treelastsome(int num,vector<string>& bar){
//更新后序几个
int n=children.size();
auto it=children.end();
while(n--){
it--;
int sts=it->second->dirasize;
if(sts>=num){
it->second->treelastsome(num,bar);
return;
}
else{
it->second->treelastsome(sts,bar);
num-=sts;
}
}
bar.push_back(name);
}
void dira::clear(){
tendescendants.clear();
children.clear();
}
int main(){
int t;
scanf("%d",&t);
while(t--){
cmdlist.clear();
dira* now=new dira("root",nullptr);//定义当前目录
int n;
scanf("%d",&n);
while(n--){
scanf("%s",tmps);
dira* ch=NULL;
command* cmd=new command(tmps);//当前的操作
switch(cmd->type){
case 0:cmd->tmpdir=now->mkdir(cmd->arg);
if(cmd->tmpdir==nullptr) printf("ERR\n");
else{
printf("OK\n");
cmdlist.push_back(cmd);
}
break;
case 1:cmd->tmpdir=now->rm(cmd->arg);
if(cmd->tmpdir==nullptr) printf("ERR\n");
else{
printf("OK\n");
cmdlist.push_back(cmd);//存储
}
break;
case 2:ch=now->cd(cmd->arg);
if(ch==nullptr) printf("ERR\n");
else{
printf("OK\n");
cmd->tmpdir=now;
now=ch;//到达上一级
cmdlist.push_back(cmd);
}
break;
case 3:now->sz();break;
case 4:now->ls();break;
case 5:now->tree();break;
case 6:{
bool success=false;
while(!success&&!cmdlist.empty()){
cmd=cmdlist.back();cmdlist.pop_back();//从链表后面取出就是最近的一次操作
switch(cmd->type){
case 0:success=now->rm(cmd->arg)!=nullptr;break;
case 1:success=now->addchild(cmd->tmpdir);break;//把删除的节点加回去
case 2:now=cmd->tmpdir;success=true;break;//返回原来所处的文件
}
}
printf(success? "OK\n":"ERR\n");
}
}
// = ->clear();
}
}
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 张牌不同,其值不同。下面依次列举了这手牌的形成规则:
大牌:这手牌不符合下面任一个形成规则。如果 α 和 β 都是大牌,那么定义它们的大小为组成这手牌的 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
思路分析
这道题和上次的限时练习的打牌那道题很相似,思路很简单,就是有一点可以注意:在这里可以观察题目要求对一些变量以结构体的形式进行封装,比如这里涉及到要比较是三带二的大小的时候,要先比三张相同的牌,再比两张相同的牌,所以可以定义结构体成员thr和tw分别进行表示,这样在进行判断牌型的时候,就可以将牌的信息记录到结构体中,进行排序的时候就会简单得多。
代码分析
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<map>
#define maxn 100005
using namespace std;
map<char,int> mp;
void initial(){
char s[]={'A','2','3','4','5','6','7','8','9','1','J','Q','K'};
for(int i=0;i<13;i++){
mp.insert(make_pair(s[i],i+1));///构造数对
}
}
int thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;
int get_pri(int num[]){
int cnt=0;
thethr=0,thetw1=0,thetw2=0,thefr=0;
for(int i=1,j=10;i<5;i++,j++){
if(num[i]==j) cnt++;
else break;
}
if(cnt==4&&num[0]==1) {
thermn=48;
//printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 8;//龙顺
}
thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;
cnt=0;
for(int i=0;i<4;i++){
if(num[i]+1==num[i+1]) cnt++;
else break;
}
if(cnt==4){
thermn=num[4];
// printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 7;//顺子
}
thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;//炸弹
if(num[3]==num[0]){
thefr=num[3];
thermn=num[4];
// printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 6;
}
if(num[4]==num[1]){
thefr=num[1];
thermn=num[0];
// printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 6;
}
thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;//三代二
if(num[0]==num[1]&&num[2]==num[4]){
thethr=num[2];
thetw1=num[0];
// printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 5;
}
if(num[0]==num[2]&&num[3]==num[4]){
thethr=num[0];
thetw1=num[3];
return 5;
}
thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;//3个
if(num[0]==num[2]){
thethr=num[0];
thermn=num[3]+num[4];
// printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 4;
}
if(num[1]==num[3]){
thethr=num[1];
thermn=num[0]+num[4];
// printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 4;
}
if(num[2]==num[4]){
thethr=num[2];
thermn=num[0]+num[1];
// printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 4;
}
thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;//两对
if(num[0]==num[1]&&num[2]==num[3]){
thetw1=num[2];//存比较大的对子
thetw2=num[0];
thermn=num[4];
// printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 3;
}
if(num[0]==num[1]&&num[3]==num[4]){
thetw1=num[3];//存比较大的对子
thetw2=num[0];
thermn=num[2];
// printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 3;
}
if(num[1]==num[2]&&num[3]==num[4]){
thetw1=num[3];//存比较大的对子
thetw2=num[1];
thermn=num[0];
//printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 3;
}
thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;//对子
if(num[0]==num[1]){
thetw1=num[0];
thermn=num[2]+num[3]+num[4];
// printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 2;
}
if(num[1]==num[2]){
thetw1=num[1];
thermn=num[0]+num[3]+num[4];
// printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 2;
}
if(num[2]==num[3]){
thetw1=num[2];
thermn=num[0]+num[1]+num[4];
// printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 2;
}
if(num[3]==num[4]){
thetw1=num[3];
thermn=num[0]+num[1]+num[2];
// printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 2;
}
thethr=0,thetw1=0,thetw2=0,thefr=0,thermn=0;//大牌
thermn=num[0]+num[1]+num[2]+num[3]+num[4];
//printf("%d,%d,%d,%d,%d\n",thethr,thetw1,thetw2,thefr,thermn);
return 1;
}
struct card{
int val[5];
int pri;//牌的类型
int thr;//三个相等的值
int tw1;//对子 三代二的二 两对
int tw2;//
int fr;//炸弹
int rmn;
string name;
card(){}
card(string nm,string cd){
name=nm;
int cnt=0;
for(int i=0;cnt<5;i++) {
val[cnt++]=mp[cd[i]];
if(cd[i]=='1') i++;
}
sort(val,val+5);
pri=get_pri(val);
thr=thethr,tw1=thetw1,tw2=thetw2,fr=thefr,rmn=thermn;
}
bool operator<(card c){
if(pri!=c.pri) return pri>c.pri;
else if(pri==8) return name<c.name;
else if(pri==7){
if(rmn!=c.rmn) return rmn>c.rmn;
else return name<c.name;
}
else if(pri==6){
if(fr!=c.fr) return fr>c.fr;
else if(rmn!=c.rmn) return rmn>c.rmn;
return name<c.name;
}
else if(pri==5){
if(thr!=c.thr) return thr>c.thr;
else if(tw1!=c.tw1) return tw1>c.tw1;
return name<c.name;
}
else if(pri==4){
if(thr!=c.thr) return thr>c.thr;
else if(rmn!=c.rmn) return rmn>c.rmn;
return name<c.name;
}
else if(pri==3){
if(tw1!=c.tw1) return tw1>c.tw1;
else if(tw2!=c.tw2) return tw2>c.tw2;
else if(rmn!=c.rmn) return rmn>c.rmn;
return name<c.name;
}
else if(pri==2){
if(tw1!=c.tw1) return tw1>c.tw1;
else if(rmn!=c.rmn) return rmn>c.rmn;
return name<c.name;
}
else{
if(rmn!=c.rmn) return rmn>c.rmn;
else return name<c.name;
}
}
}c[maxn];
int main(){
initial();
int n;
while(~scanf("%d",&n)){
for(int i=0;i<n;i++){
string nm,cd;
cin>>nm>>cd;
c[i]=card(nm,cd);
}
sort(c,c+n);
for(int i=0;i<n;i++)
cout<<c[i].name<<"\n";
}
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
思路分析
这个题目一定要好好读,“k = 所有椅子上的人数的最大值,那么k可能的最大值mx和最小值mn分别是多少。”这句话一定要好好咀嚼,我因为题意没理解清除WA了好几次。找到一种坐法使得坐的人数最多的那个椅字人数达到最大,这个很简单,肯定是max(a[0:x-1])+y。现在要求最小情况mn,那么max(a[0:x-1])一定成立的,所以不妨这样思考,将y人坐之前的椅字按照人数升序排序,然后让y人将每个不足max(a[0:x-1])人的椅子坐满到max(a[0:x-1])人。那么此时,每个椅子上的人都是max(a[0:x-1])个,之后只需将y人中还剩下rmn的人平均分配到椅子中,即mn=max(a[0:x-1])+mn/x+(mn%x? 1:0)。
代码实现
#include<bits/stdc++.h>
using namespace std;
int a[110];
int main(){
int x,y,sum=0;
scanf("%d%d",&x,&y);
for(int i=0;i<x;i++){
scanf("%d",&a[i]);
sum+=a[i];
}
sort(a,a+x);
int tp=a[x-1];//已有的最大人数
int mx=tp+y;
int mn=tp;
int lck=x*tp-sum;//先将每个椅子的人数填补到tp个 ,如果y不够填补,那么就直接输出mn,mx
if(y>lck) mn=tp+(y-lck)/x+((y-lck)%x?1:0);//说明能够填补满之后将剩余的数字平均填补到长椅中
printf("%d %d",mn,mx);
return 0;
}