程序设计思维与实践 Week9 作业(咕咕东的目录管理器、东东学打牌、签到题(长凳))

**

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;
 } 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值