题目一 咕咕咚的目录管理器
题目描述
咕咕东的雪梨电脑的操作系统在上个月受到宇宙射线的影响,时不时发生故障,他受不了了,想要写一个高效易用零bug的操作系统 —— 这工程量太大了,所以他定了一个小目标,从实现一个目录管理器开始。前些日子,东东的电脑终于因为过度收到宇宙射线的影响而宕机,无法写代码。他的好友TT正忙着在B站看猫片,另一位好友瑞神正忙着打守望先锋。现在只有你能帮助东东!
初始时,咕咕东的硬盘是空的,命令行的当前目录为根目录 root。
目录管理器可以理解为要维护一棵有根树结构,每个目录的儿子必须保持字典序。
input
输入文件包含多组测试数据,第一行输入一个整数表示测试数据的组数 T (T <= 20);
每组测试数据的第一行输入一个整数表示该组测试数据的命令总数 Q (Q <= 1e5);
每组测试数据的 2 ~ Q+1 行为具体的操作 (MKDIR、RM 操作总数不超过 5000);
面对数据范围你要思考的是他们代表的 “命令” 执行的最大可接受复杂度,只有这样你才能知道你需要设计的是怎样复杂度的系统。
output
每组测试数据的输出结果间需要输出一行空行。注意大小写敏感。
时空限制
Time limit 6000 ms
Memory limit 1048576 kB
example
input 1
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 1
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
做法与思路
大模拟类的题目最重要的一点大概就是使用合适的数据结构。
显然,使用树形结构来表示文件目录是较为合适的,树的每一个节点即为一个目录(dir)。
那么该节点需要有如下信息:目录名,该目录的直接子目录索引,该目录的父目录(许多操作要回溯到父目录),该目录的大小,该目录是否被更新过(之后再解释这个变量的作用),该目录前序遍历的十个目录名(之后再进行解释)。
目录的直接子目录可以使用map来实现,将(子目录的名字)string->子目录(dir)对应起来。
我们一步步的分析需要的操作。
1.mkdir,直接在该目录的直接子目录索引中添加该目录(同时为该子目录开辟空间)即可。
2.rm,删除目录,值得注意的是,由于存在撤销操作,我们不能直接将这个子目录删除,而是如同现代操作系统删除文件一样,只是删除目录对应的索引,而目录本身还是存在的,如果撤销操作则重新建立索引即可。
3.cd,进入一个目录,如果不是…则根据map索引查找,如果是…则根据父目录指针到达父目录。
4.sz,输出目录大小。这里跟前面的要搭配进行,在执行mkdir操作后,应该回溯其父目录直到根目录,使过程中经过的节点全部+1.rm操作也是类似的,不过是使所有节点-1
5.ls,输出直接子目录名。这个比较简单,map类中的string本身就已经按照字典序排列,小于十个直接输出,大于则利用迭代器分别输出前五个和后五个。
6.tree,这个可以说是最麻烦的操作,尽管本身看起来很简单,直接进行前序遍历即可,但是通过观察数据,该指令可能有1e5-5000条,如果每次都要遍历规模为5000的树,显然超时。所以我们要使用懒更新,即存储好该目录前序遍历的结果,如果遍历该目录时,该目录的结构没有发生过变化,那么直接输出之前存储的结果即可。否则,重新遍历存储输出。
所以要维护一个变量judge来判断该目录是否发生更新,显然的,mkdir,rm以及这两个操作的undo操作都会导致该目录一直回溯到根目录的更新。
值得一提的是,遍历时对于最后五个目录要倒着进行遍历,如果从头开始遍历到尾依旧会超时(太过分了)。
7.undo,该操作介绍之前操作的时候已经说明的差不多了。这里主要说明一下实现所用的数据结构,使用数组和栈来实现弹出顶部命令均可。但这里又涉及到命令所需要的数据结构。命令需要包含命令种类、命令作用的目录名,命令作用的目录的指针(*dir),这里需要说明目录指针完全可以单独开一个数组进行存储,删除索引时不删除目录指针,但为了方便操作我把这个数组直接整合到了命令数组里,因为命令和目录指针是多对一的关系。
代码
#include <iostream>
#include <map>
#include <algorithm>
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
//每个指令都返回对应的目录,删除则返回被删除的目录指针,增加则返回增加的目录指针,cd则返回进入的目录指针,然后将该指针存储到对应的指令中,方便撤回操作
struct dir{
string name;//目录名
map<string,dir*> children;//存储子目录的目录名和对应的子目录指针
dir* parent;//父目录指针
bool judge;//判断是否更新过
int size;//方便判断数量
vector<string> ten;//前序遍历得到的前五个和后五个结果
dir(string tname="",dir* tparent=NULL)
{
name=tname; parent=tparent; judge=1; size=1; children.clear();
}
void update(int num)
{
judge=1;
size+=num;
if(parent!=NULL)
{
parent->update(num);
}
}
dir* mkdir(string cname)
{
map<string,dir*>::iterator iter=children.find(cname);
if(iter!=children.end())
{
return NULL;
}
dir* tem=new dir(cname,this);
children[cname]=tem;
update(1);
return tem;
}
dir* rm(string tname)//只删除索引,dir依旧存储在指令序列中
{
map<string,dir*>::iterator iter=children.find(tname);
if(iter==children.end())
{
return NULL;
}
dir* tem=iter->second;
update(-iter->second->size);
children.erase(iter);
return tem;
}
dir* rrm(dir* p)//删除目录的逆操作
{
//cout<<33<<p->name<<endl;
map<string,dir*>::iterator iter=children.find(p->name);
if(iter!=children.end())
{
// cout<<22<<iter->second->name<<endl;
return NULL;
}
children[p->name]=p;
update(p->size);
return p;
}
dir* cd(string tname)
{
// cout<<11<<"tname为"<<tname<<endl;
if(tname!="..")
{
map<string,dir*>::iterator iter=children.find(tname);
if(iter==children.end())
{
//cout<<11<<endl;
return NULL;
}
else return iter->second;
}
else
{
return parent;
}
}
void sz()
{
printf("%d\n",size);
}
void ls()
{
if(children.empty())
{
printf("EMPTY\n");
return;
}
if(children.size()<=10)
{
map<string,dir*>::iterator iter=children.begin();
for(iter;iter!=children.end();iter++)
{
printf("%s\n",iter->first.c_str());
}
}
else
{
int n=1;
int tn=children.size();
map<string,dir*>::iterator iter=children.begin();
for(int i=0;i<5;i++,iter++)
{
printf("%s\n",iter->first.c_str());
}
printf("...\n");
iter=children.end();
for(int i=0;i<5;i++) iter--;
for(iter;iter!=children.end();iter++)
{
printf("%s\n",iter->first.c_str());
}
}
}
void tree()
{
if(size==1)
{
printf("EMPTY\n");
return;
}
if(judge==1)
{
ten.clear();
if(size<=10)
{
tree1(ten);
for(int i=0;i<ten.size();i++) printf("%s\n",ten[i].c_str());
}
else
{
tree2(ten);
tree3(ten);
for(int i=0;i<5;i++) printf("%s\n",ten[i].c_str());
printf("...\n");
for(int i=9;i>=5;i--) printf("%s\n",ten[i].c_str());
}
judge=0;
}
else
{
if(size<=10)
{
for(int i=0;i<ten.size();i++) printf("%s\n",ten[i].c_str());
}
else
{
for(int i=0;i<5;i++) printf("%s\n",ten[i].c_str());
printf("...\n");
for(int i=9;i>=5;i--) printf("%s\n",ten[i].c_str());
}
}
}
void tree1(vector<string>& shi)
{
shi.push_back(name);
map<string,dir*>::iterator iter=children.begin();
for(iter;iter!=children.end();iter++)
{
iter->second->tree1(shi);
}
}
void tree2(vector<string>& shi)//前五个
{
if(shi.size()==5)
{
return ;
}
shi.push_back(name);
map<string,dir*>::iterator iter=children.begin();
for(iter;iter!=children.end();iter++)
{
if(shi.size()==5)
{
return;
}
else iter->second->tree2(shi);
}
}
void tree3(vector<string>& shi)//后五个
{
//cout<<"来了"<<name<<endl;
if(shi.size()==10)
{
return;
}
map<string,dir*>::iterator iter=children.end();
//cout<<children.end()->second->name<<endl;
for(int i=0;i<children.size();i++)
{
iter--;
if(shi.size()==10)
{
return;
}
else iter->second->tree3(shi);
// cout<<"laile"<<name<<endl;
}
shi.push_back(name);
}
};
struct instr{
string zlm;//指令名
string name;//目录名
dir* save;//目录指针
instr()
{
name=""; save=NULL;
}
// instr(string tem)
// {
//if(tem==)
// }
};instr a[100100];
int main(int argc, char** argv) {
int zu;
scanf("%d",&zu);
for(int ci=0;ci<zu;ci++)
{
dir root;
root.name="root";
dir* now=&root;
int n=0;//记录指令栈的元素个数
int numble;
scanf("%d",&numble);
for(int i=0;i<numble;i++)
{
string tem;
char ttem[10];
scanf("%s",ttem);
tem=ttem;
if(tem=="MKDIR")
{
a[n].zlm=tem;
scanf("%s",ttem);
a[n].name=ttem;
n++;
a[n-1].save=now->mkdir(a[n-1].name);
if(a[n-1].save==NULL)
{
printf("ERR\n");
n--;
}
else printf("OK\n");
}
else if(tem=="RM")
{
a[n].zlm=tem;
scanf("%s",ttem);
a[n].name=ttem;
n++;
a[n-1].save=now->rm(a[n-1].name);
if(a[n-1].save==NULL)
{
printf("ERR\n");
n--;
}
else printf("OK\n");
}
else if(tem=="CD")
{
//cout<<"进入cd"<<endl;
a[n].zlm=tem;
scanf("%s",ttem);
a[n].name=ttem;
n++;
a[n-1].save=now;
//cout<<a[n-1].name<<endl;
dir* tt=now->cd(a[n-1].name);
if(tt==NULL)
{
printf("ERR\n");
n--;
}
else
{
printf("OK\n");
now=tt;//更新now
}
}
else if(tem=="SZ")
{
now->sz();
}
else if(tem=="LS")
{
now->ls();
}
else if(tem=="TREE")
{
now->tree();
}
else if(tem=="UNDO")
{
if(n==0)
{
printf("ERR\n");
}
else
{
n--;
if(a[n].zlm=="MKDIR")
{
if(now->rm(a[n].name)==NULL)
{
printf("ERR\n");
}
else printf("OK\n");
}
else if(a[n].zlm=="RM")
{
//cout<<11<<endl;
if(now->rrm(a[n].save)==NULL)
{
printf("ERR\n");
}
else printf("OK\n");
}
else if(a[n].zlm=="CD")
{
now=a[n].save;
printf("OK\n");
}
}
}
}
printf("\n");
}
return 0;
}
题目二 东东学打牌
题目描述
最近,东东沉迷于打牌。所以他找到 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 <= 1e5),表明全场共多少人。
随后是 n 行,每行一个字符串 s1 和 s2 (1 <= |s1|,|s2| <= 10), s1 是对应人的名字,s2 是他手里的牌情况。
output
对于每组测试数据,输出 n 行,即这次全场人的排名。
example
input 1
3
DongDong AAA109
ZJM 678910
Hrz 678910
output 1
Hrz
ZJM
DongDong
做法与思路
这个题与上一个题相比难度降低了太多,而且与之前做过一个情景类似的题相比也是更为简单。
首先读入每个人的牌,可以利用map数组将对应的字符转化为数字,方便之后的比较。
对每个人的牌进行大小排序,然后判断属于哪种类型,这里也存在一个优先级,优先级最多存在四层,如22334这种情况,它的第一优先级为2(两对),第二优先级为3(33这个对子),第三优先级为2(22这个对子),第四优先级为4(单牌)。判断大小时先判断谁的第一优先级大,如果相同则判断谁的第二优先级大,以此类推。
代码
#include <iostream>
#include <cstring>
#include <map>
#include <algorithm>
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
map<char,int> change;
struct person{
string name;
string st;
int tag;//标号
int rank1;//一层大小
int rank2;//二层大小
int rank3;//三层大小
int w;
person()
{
name="";
st="";
tag=0;rank1=0;rank2=0;rank3=0;w=0;
}
void clean()
{
name="";
st="";
tag=0;
rank1=0;
rank2=0;
rank3=0;
w=0;
}
void print(){
cout<<"name"<<name<<endl<<"tag "<<tag<<endl<<"rank1 "<<rank1<<endl<<"rank2 "<<rank2<<endl<<"rank3 "<<rank3<<endl;
}
};
person p[100100];
void check_tag(person &per)
{
string tem=per.st;
int a[5];
for(int i=0;i<tem.size();i++)
{
if(tem[i]=='1')
{
tem[i]='T';
}
}
int t=0;
for(int i=0;i<tem.size();i++)
{
if(tem[i]!='0')
{
a[t]=change[tem[i]];
t++;
}
}
sort(a,a+5);
// cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<" "<<a[3]<<" "<<a[4]<<endl;
if(a[1]==10&&a[2]==11&&a[3]==12&&a[4]==13&&a[0]==1)
{
per.tag=8;
return;
}
else if(a[1]==a[0]+1&&a[2]==a[1]+1&&a[3]==a[2]+1&&a[4]==a[3]+1)
{
per.tag=7;
per.rank1=a[4];
return;
}
else if(a[0]==a[1]&&a[1]==a[2]&&a[2]==a[3])
{
per.tag=6;
per.rank1=a[0];
per.rank2=a[4];
return;
}
else if(a[3]==a[4]&&a[1]==a[2]&&a[2]==a[3])
{
per.tag=6;
per.rank1=a[3];
per.rank2=a[0];
return;
}
else if(a[0]==a[1]&&a[1]==a[2]&&a[3]==a[4])
{
per.tag=5;
per.rank1=a[0];
per.rank2=a[3];
return;
}
else if(a[2]==a[3]&&a[3]==a[4]&&a[1]==a[0])
{
per.tag=5;
per.rank1=a[2];
per.rank2=a[0];
return;
}
else if(a[0]==a[1]&&a[1]==a[2])
{
per.tag=4;
per.rank1=a[0];
per.rank2=a[3]+a[4];
return ;
}
else if(a[2]==a[3]&&a[3]==a[4])
{
per.tag=4;
per.rank1=a[2];
per.rank2=a[0]+a[1];
return;
}
else if(a[1]==a[2]&&a[2]==a[3])
{
per.tag=4;
per.rank1=a[1];
per.rank2=a[0]+a[4];
return ;
}
else if(a[0]==a[1]&&a[2]==a[3])
{
per.tag=3;
per.rank1=max(a[0],a[2]);
per.rank2=min(a[0],a[2]);
per.rank3=a[4];
return ;
}
else if(a[0]==a[1]&&a[3]==a[4])
{
per.tag=3;
per.rank1=max(a[0],a[3]);
per.rank2=min(a[0],a[3]);
per.rank3=a[2];
return ;
}
else if(a[1]==a[2]&&a[3]==a[4])
{
per.tag=3;
per.rank1=max(a[1],a[3]);
per.rank2=min(a[1],a[3]);
per.rank3=a[0];
return ;
}
else if(a[0]==a[1])
{
per.tag=2; per.rank2=a[2]+a[3]+a[4]; per.rank1=a[0];
return ;
}
else if(a[1]==a[2])
{
per.tag=2; per.rank2=a[0]+a[3]+a[4]; per.rank1=a[1];
return ;
}
else if(a[2]==a[3])
{
per.tag=2; per.rank2=a[1]+a[4]+a[0]; per.rank1=a[2];
return;
}
else if(a[3]==a[4])
{
per.tag=2; per.rank2=a[0]+a[1]+a[2]; per.rank1=a[3];
return;
}
else
{
per.tag=1; per.rank1=a[0]+a[1]+a[2]+a[3]+a[4];
return;
}
}
bool judge(person a,person b)
{
if(a.w!=b.w)
{
return a.w<b.w;
}
else return a.name>b.name;
}
int main(int argc, char** argv) {
change['A']=1;
change['T']=10;
change['J']=11;
change['Q']=12;
change['K']=13;
for(int i=2;i<=10;i++)
{
change[char('0'+i)]=i;
}
int n;
while(cin>>n)
{
for(int ci=0;ci<n;ci++)
{
p[ci].clean();
string name;
cin>>name;
string tem;
cin>>tem;
p[ci].name=name;
p[ci].st=tem;
check_tag(p[ci]);
p[ci].w=p[ci].rank3+p[ci].rank2*100+p[ci].rank1*10000+p[ci].tag*1000000;
// cout<<p[ci].tag<<" "<<p[ci].rank1<<" "<<p[ci].rank2<<" "<<p[ci].rank3<<endl;
// p[ci].print();
}
sort(p,p+n,judge);
for(int ci=n-1;ci>=0;ci--)
{
cout<<p[ci].name<<endl;
}
}
return 0;
}
题目三 签到题
题目描述
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
example
input 1
3
7
1
6
1
output 1
6 13
做法与思路
虽然是个签到题但是我还wrong了四次…
其实思路很简单,尽可能的使所有座位上的人数相同,可以用多种思路实现该操作,我的思路是先尽可能把低于最高人数的椅子填充到与最高人数的椅子人数相同,如果还没全部填充完毕就人数耗尽,则直接输出最高人数椅子即可;否则把剩余的人数平摊到每个椅子上,除不尽向上取1.
代码
#include <iostream>
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int a[200];
int main(int argc, char** argv) {
int x,y;
cin>>x>>y;
int maxx=0;
for(int i=0;i<x;i++)
{
int tem;
cin>>tem;
if(tem>maxx)
{
maxx=tem;
}
a[i]=tem;
}
int maxx2=maxx+y;
for(int i=0;i<x;i++)
{
int tem=maxx-a[i];
y-=tem;
}
if(y<=0) cout<<maxx<<" "<<maxx2;
else
{
int ttem;
int tem=y%x;
if(tem==0)
{
ttem=y/x;
}
else ttem=y/x+1;
maxx+=ttem;
cout<<maxx<<" "<<maxx2;
}
return 0;
}