文章目录
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
样例输入:
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
题目分析:
分析命令:
-
首先对每条指令,每个指令有指令的类型以及MKDIR,RM,CD三个指令还有参数,所以用Command结构体进行封装。
-
又因为目录管理器是一棵目录树的数据结构,所以需要维护该树,又因为要求字典序,所以要用map<string,目录>,它可以根据key也就是string 在内部进行排序,用类Directory表示。
-
具体实现:
- MKDIR创建子目录操作:创建子目录并返回,创建失败返回空指针。
- RM删除子目录操作:删除子目录并返回,删除失败返回空指针。
- CD进入子目录操作:当遇到…的时候,返回其父节点即上一级目录。
- SZ输出当前目录的大小操作:输出当前指针所在的subtreesize即可。
- LS输出多行表示当前目录的直接目录名操作:当当前目录长度为0时,说明当前目录为空,输出EMPTY;当子目录数量小于等于10的时候,迭代器遍历全部输出;当子目录数量大于10的时候,分别用迭代器从前遍历输出前5个和用迭代器从后遍历,输出后5个。
- TREE输出以当前目录为根的子树的前序遍历操作:这是最难的一部分,因为在后代节点数量大于10的时候,要开始前序遍历和后序遍历, 并且对复杂度有要求! TREE的命令条数最多约为1e5,此时整个树最多5000 个节点 , 20 * 1e5 * 5000,你发现可能会TLE 。所以需要用到缓存(懒更新),节点数远少于TREE 操作数,指不定还有重复询问,对于目录相同期间问过的相同问题,理应只有一次是计算过程。保存当前节点点的“十个后代”,记录当前节点的子树有无变动,无变动则“十个后代”无需更新,更新操作分为更新全桶,更新前序几个和更新后序几个。
- UNDO撤销操作:撤销操作针对三个操作,MKDIR,RM,CD;且这三种操作必须是执行成功才能执行撤销。它们的撤销操作分别为删掉rm,添加addchild,回退。为了执行UNDO 需要保存每条指令执行的执行结果,保存到-> struct Command{…}中。
代码:
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<map>
#include<string>
#include<vector>
using namespace std;
char tmps[20];//声明一个辅助输入的字符串数组
struct Directory
{
string name;//当前目录的名字
map<string, Directory*>children;
Directory* parent;//以备CD.. 返回上级目录
int subtreeSize;//以备sz要输出子树大小
vector<string>* tenDescendants;//保存当前节点的十个后代
Directory(string name, Directory* parent)
{
this->name = name;
this->parent = parent;
this->subtreeSize = 1;
this->tenDescendants=new vector<string>;
}
bool updated;//记录当前节点的子孙有无变动,无变动则十个后代无需更新
public:
void maintain(int delta) //向上维护子树大小
{
updated = true;
subtreeSize += delta;
if (parent != nullptr)
{
parent->maintain(delta);
}
}
void tree()
{
if (subtreeSize == 1) printf("EMPTY\n");
else if (subtreeSize <= 10)
{
if (this->updated)
{
tenDescendants->clear();
treeAll(tenDescendants);
this->updated = false;
}
for (int i = 0; i < subtreeSize; i++)
{
printf("%s\n", tenDescendants->at(i).c_str());
}
}
else
{
if (this->updated)
{
tenDescendants->clear();
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());
}
}
}
Directory* getChild(string name) //取子目录并返回,不存在返回空指针
{
auto it = children.find(name);
if (it == children.end()) return nullptr;
return it->second;
}
Directory* mkdir(string name) //创建子目录并返回,创建失败返回空指针
{
if (children.find(name) != children.end()) return nullptr;
Directory* ch = new Directory(name, this);
children[name] = ch;
maintain(+1);
return ch;
}
Directory* rm(string name) //删除子目录并返回,删除失败返回空指针
{
auto it = children.find(name);
if (it == children.end()) return nullptr;
maintain(-1 * it->second->subtreeSize);
children.erase(it);
return it->second;
}
Directory* cd(string name)
{
if (".." == name)
{
return this->parent;
}
return getChild(name);
}
bool addChild(Directory* ch)//加入子目录并判断成功与否
{
if (children.find(ch->name) != children.end())
{
return false;
}
children[ch->name] = ch;
maintain(+ch->subtreeSize);
return true;
}
void sz()
{
printf("%d\n", this->subtreeSize);
}
void 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
{
auto 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());
}
}
}
private:
void treeAll(vector<string>* bar) //更新全桶
{
bar->push_back(name);
for (auto &entry : children)
{
entry.second->treeAll(bar);
}
}
void 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->subtreeSize;
if (sts >= num)
{
it->second->treeFirstSome(num, bar);
return;
}
else
{
it->second->treeFirstSome(sts, bar);
num -= sts;
}
it++;
}
}
void treeLastSome(int num, vector<string>* bar)//更新后序几个
{
int n = children.size();
auto it = children.end();
while (n--)
{
it--;
int sts = it->second->subtreeSize;
if (sts >= num)
{
it->second->treeLastSome(num, bar);
return;
}
else
{
it->second->treeLastSome(sts, bar);
num -= sts;
}
}
bar->push_back(name);
}
};
struct Command
{
int type;//命令的类型
string arg;//命令的参数
const string CMDNAMES[7] = { "MKDIR","RM","CD","SZ","LS","TREE","UNDO" };
Command(string s)
{//构造函数
for(int i=0;i<7;i++)
if (CMDNAMES[i] == s)
{
type = i;
if (i < 3) //MKDIR、RM、CD的参数后续读入
{
scanf("%s", tmps), arg = tmps;
}
return;
}
}
Directory* tmpDir;//记录刚刚操作涉及的目录节点
};
void solve()
{
int n;
cin>>n;
//scanf("%d",&n); //每组数据有m行命令
Directory *now = new Directory("root", nullptr);
vector<Command*>cmdList;//新增加的数组存成功执行的命令以备undo
while (n--)
{
scanf("%s", tmps);
Command* cmd = new Command(tmps);
switch (cmd->type)
{
case 0://MKDIR
{
cmd->tmpDir = now->mkdir(cmd->arg);
if (cmd->tmpDir == nullptr) printf("ERR\n");
else
{
printf("OK\n");
cmdList.push_back(cmd);
}
break;
}
case 1://RM
{
cmd->tmpDir = now->rm(cmd->arg);
if (cmd->tmpDir == nullptr) printf("ERR\n");
else
{
printf("OK\n");
cmdList.push_back(cmd);
}
break;
}
case 2://CD
{
Directory * 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://SZ
now->sz();
break;
case 4://LS
now->ls();
break;
case 5://TREE
now->tree();
break;
case 6://UNDO
{
bool success = false;//undo执行成功与否
while (!success && !cmdList.empty())
{
cmd = cmdList.back(); cmdList.pop_back();
switch (cmd->type)
{
case 0://UNDO MKDIR
success = now->rm(cmd->arg) != nullptr;
break;
case 1://UNDO RM
success = now->addChild(cmd->tmpDir);
break;
case 2: //UNDO CD
now = cmd->tmpDir;
success = true;
break;
}
}
printf(success ? "OK\n" : "ERR\n");
}
}
}
printf("\n");
}
int main()
{
int T;
//scanf("%d",&T);//1、测试有T组数据
cin>>T;
while(T--)
{
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 张牌不同,其值不同。下面依次列举了这手牌的形成规则:
大牌:这手牌不符合下面任一个形成规则。如果 α 和 β 都是大牌,那么定义它们的大小为组成这手牌的 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
题目分析:
首先我认为该题的关键在于对不同类型的牌型的排序方式,总的来看对八种牌型的多关键字排序最多有4个关键字,所以对每个玩家构建一个结构体,变量包括该玩家手中的牌值数组card[],牌型status,玩家姓名name,以及三个排序关键字b,c,d,重构<,令其先按牌型值status升序排序,牌型值相同则依次按照b,c,d,进行降序排序,最后再按name的字典序升序排序。
然后正确的将输入的牌转换为题目要求的牌值大小,然后再判断牌型,并根据牌型更新关键字,最后排序即可。
(判断牌型的时候一定要仔细!非常仔细!!!)
代码:
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=1e5+3;
struct player
{
string name;//姓名
int card[6];//牌面
int status;//一手牌的类型
int b,c,d;//分别表示两张牌 三张牌 一张牌的大小
}poke[maxn];
bool cmp(player x,player y)
{
if(x.status!=y.status)
{
return x.status>y.status;
}
else if(x.b!=y.b)
{
return x.b>y.b;
}
else if(x.c!=y.c)
{
return x.c>y.c;
}
else if(x.d!=y.d)
{
return x.d>y.d;
}
else
{
return x.name<y.name;
}
}
void trans(string s,int cnt)//牌面值的转换
{
memset(poke[cnt].card,0,sizeof(poke[cnt].card));
int k=0;
for(int i=0;i<s.size();i++)
{
if(s[i]=='A')
{
poke[cnt].card[k]=1;
}
else if(s[i]>='2'&&s[i]<='9')
{
poke[cnt].card[k]=s[i]-'0';
}
else if(s[i]=='1')
{
poke[cnt].card[k]=10;
i++;
}
else if(s[i]=='J')
{
poke[cnt].card[k]=11;
}
else if(s[i]=='Q')
{
poke[cnt].card[k]=12;
}
else if(s[i]=='K')
{
poke[cnt].card[k]=13;
}
k++;
}
}
void solve(int cnt)//对每个人手中的牌进行得分的判断
{
sort(poke[cnt].card,poke[cnt].card+5);
int sum=0;
for(int i=0;i<5;i++)
{
sum+=poke[cnt].card[i];
}
bool shunzi=true;
for(int i=0;i<4;i++)
{
if(poke[cnt].card[i+1]!=poke[cnt].card[i]+1)
{
shunzi=false;
}
}
int num[5];
memset(num,0,sizeof(num));
for(int i=0;i<5;i++)
{
for(int j=0;j<5;j++)
{
if(poke[cnt].card[i]==poke[cnt].card[j])
{
num[i]++;
}
}
}
sort(num,num+5);
if(poke[cnt].card[0]==1&&poke[cnt].card[1]==10&&poke[cnt].card[2]==11&&poke[cnt].card[3]==12&&poke[cnt].card[4]==13)
{
poke[cnt].status=8;//8.龙顺子
}
else if(num[0]==1&&num[1]==2&&num[4]==2)
{
poke[cnt].status=3;//3.两队
int *maxx=new int[5];int r=0;
memset(maxx,0,sizeof(maxx));
for(int i=0;i<4;i++)
{
if(poke[cnt].card[i]==poke[cnt].card[i+1])
{
maxx[r]=poke[cnt].card[i];
r++;
}
}
poke[cnt].b=maxx[1];
poke[cnt].c=maxx[0];
poke[cnt].d=sum-2*maxx[1]-2*maxx[0];
}
else if(num[4]==2&&num[1]==1)
{
poke[cnt].status=2;//2.对子
int maxx=0;
for(int i=0;i<4;i++)
{
if(poke[cnt].card[i]==poke[cnt].card[i+1])
{
maxx=poke[cnt].card[i];
}
}
poke[cnt].b=maxx;
poke[cnt].c=sum-2*maxx;
}
else if(num[4]==3&&num[0]==2)
{
poke[cnt].status=5;//三带二
int maxx=0;
for(int i=0;i<4;i++)
{
if(poke[cnt].card[i]==poke[cnt].card[i+1]&&poke[cnt].card[i+1]==poke[cnt].card[i+2])
{
maxx=poke[cnt].card[i];
}
}
poke[cnt].b=maxx;
poke[cnt].c=(sum-3*maxx)/2;
}
else if(num[4]==3&&num[0]==1&&num[1]==1)
{
poke[cnt].status=4;//三个
int maxx=0;
for(int i=0;i<4;i++)
{
if(poke[cnt].card[i]==poke[cnt].card[i+1])
{
maxx=poke[cnt].card[i];
}
}
poke[cnt].b=maxx;
poke[cnt].c=sum-3*maxx;
}
else if(num[4]==4&&num[0]==1)
{
poke[cnt].status=6;//炸弹
int maxx=0;
for(int i=0;i<4;i++)
{
if(poke[cnt].card[i]==poke[cnt].card[i+1])
{
maxx=poke[cnt].card[i];
}
}
poke[cnt].b=maxx;
poke[cnt].c=sum-4*maxx;
}
else if(shunzi)
{
poke[cnt].status=7;
poke[cnt].b=poke[cnt].card[4];
}
else
{
poke[cnt].status=1;
poke[cnt].b=sum;
}
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<n;i++)
{
string s2;//s1名字 s2手中的牌
cin>>poke[i].name>>s2;
poke[i].status=poke[i].b=poke[i].c=poke[i].d=0;
trans(s2,i);
solve(i);
}
sort(poke,poke+n,cmp);
for(int i=0;i<n;i++)
{
cout<<poke[i].name<<endl;
}
}
return 0;
}
/*
5
DongDong AAA109
ZJM 678910
HRZ 78910J
TT 10JQKA
XX AAA56
*/
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。
题目分析:
- 首先 最大值无疑就是让y个人全部坐在a[i]最大的那个长椅上,即mx=k+y。
- 然后最小值就是要让每个长椅上的人尽可能地均匀,有两种情况:
(1)当y不足以分配给<k的其他长椅时,那么最小值就是k;
(2)当y可以分配给<k的其他长椅时,先分配,即让所有的长椅的人数都达到k,然后剩下的人平均分给x个长椅,最后最多的那个长椅人数即为mn。
代码:
#include<iostream>
#include<stdio.h>
using namespace std;
int main()
{
int x;
cin>>x;//长椅数目
int *a=new int[x];
int y;
cin>>y;//有 y 个人来到公园
int mn=0,mx=0;
int k=0;
for(int i=0;i<x;i++)
{
cin>>a[i];//初始时公园长椅上坐着的人数
if(a[i]>k)
{
k=a[i];
}
}
int ans=0,cnt=0;
for(int i=0;i<x;i++)
{
if(a[i]==k)
{
ans++;
}
else
{
cnt+=a[i];
}
}
if((cnt+y)>(x-ans)*k)
{
int temp=y-((x-ans)*k-cnt);
if(temp%x==0)
{
mn=temp/x+k;
}
else
{
mn=temp/x+1+k;
}
}
else
{
mn=k;
}
mx=k+y;
cout<<mn<<" "<<mx<<" "<<endl;
return 0;
}