一、大纲
本周作业与实验题目如下:
- 咕咕东的目录管理器(封装)
- 东东学打牌
- 签到题(思维训练)
二、逐个击破
1.目录管理器
题目描述
咕咕东的雪梨电脑的操作系统在上个月受到宇宙射线的影响,时不时发生故障,他受不了了,想要写一个高效易用零bug的操作系统 —— 这工程量太大了,所以他定了一个小目标,从实现一个目录管理器开始。前些日子,东东的电脑终于因为过度收到宇宙射线的影响而宕机,无法写代码。他的好友TT正忙着在B站看猫片,另一位好友瑞神正忙着打守望先锋。现在只有你能帮助东东!
初始时,咕咕东的硬盘是空的,命令行的当前目录为根目录 root。
目录管理器可以理解为要维护一棵有根树结构,每个目录的儿子必须保持字典序。
现在咕咕东可以在命令行下执行以下表格中描述的命令:
- Input
输入文件包含多组测试数据,第一行输入一个整数表示测试数据的组数 T (T <= 20);
每组测试数据的第一行输入一个整数表示该组测试数据的命令总数 Q (Q <= 1e5);
每组测试数据的 2 ~ Q+1 行为具体的操作 (MKDIR、RM 操作总数不超过 5000);
- Output
每组测试数据的输出结果间需要输出一行空行。注意大小写敏感。
题目分析
这道题目相对于常规的程序设计题而言是一道体量较大的题目,但是总体的目标是非常清晰的,考虑到一条命令不单有命令形式,还有命令参数,比如“MKDIR s”,等会我们肯定还要进行参数的分离,同类信息最好内聚,所以采用封装的方法,这里构造两个结构体,第一个结构体为Command,用于确定命令的类型和命令的参数,同时还要记录上一步操作设计的目录节点,用于undo的时候的逆操作。具体结构体定义如下:
struct Command
{
const string CMD_NAMES[7] = {"MKDIR","RM","CD","SZ","LS","TREE","UNDO"};
int type;//命令类型
string arg;//命令的参数
Command(const string &s)
{
for(int i=0;i<7;i++)
{
if(CMD_NAMES[i]==s)
{
this->type = i;
if(i<3) scanf("%s",tmps),arg=tmps;
return;
}
}
}
Directory* tmpDir; //记录刚刚操作涉及的目录节点
};
第二个结构体为Directory,包括当前目录的名字、当前目录的子目录、父目录、子树的大小和所有命令的具体操作函数等信息。这里详细描述一下 t r e e ( ) tree() tree()命令的实现,因为在后代节点数量大于10 时,要开始前序遍历和后序遍历,但是对于复杂度进行大致估算以后会发现如果我们对于每一次 t r e e ( ) tree() tree()命令都进行遍历那么会TLE,因此必须考虑去优化这个方法,这里就非常巧妙的运用的一种技巧:缓存,节点数远少于TREE 操作数,指不定还有重复询问,对于目录相同期间问过的相同问题,理应只有一次是计算过程。有些类似计算机组成原理中局部性原理,一个目录被访问过以后在近期的一段时间内大概率会再次访问。结构体定义如下:
struct Directory{
string name; //当前目录的名字
map<string,Directory*> children; //用指针是避免复制构造造成的浪费内存
Directory* parent; //以备"CD.."要返回上级目录
int subtreeSize; //以备"SZ"要输出子树大小
vector<string>* tenDescendants;//缓存10个后代
bool updated;//子树有没有发生更新
Directory(string name, Directory* parent)
{
this->name = name;
this->parent = parent;
this->subtreeSize = 1;
updated = false;
children.clear();
tenDescendants = new vector<string>;//这个地方一定要注意定义新的目录的时候要初始化
}
public:
bool addChild(Directory* ch)
{
if(children.find(ch->name) != children.end())
return false;
children[ch->name] = ch;
maintain(ch->subtreeSize);
return true;
}
Directory* getChild(const string &name)
{
auto it = children.find(name);
if(it == children.end())
return nullptr;
return it->second;
}
Directory* mkdir(const 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(const 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(const string &name)
{
if(".."==name)//返回父亲
return this->parent;
return getChild(name);
}
void maintain(int delta)
{//向上维护
updated = true;
subtreeSize += delta;
if(parent != nullptr)
parent->maintain(delta);
}
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());
}
}
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();
treeFirst(5,tenDescendants);
treeLast(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());
}
}
private:
void treeAll(vector<string>* bar)
{
bar->push_back(name);
for(auto &entry:children)
entry.second->treeAll(bar);
}
void treeLast(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->treeLast(num,bar);
return;
}
else
{
it->second->treeLast(sts,bar);
num -=sts;
}
}
bar->push_back(name);
}
void treeFirst(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->treeFirst(num,bar);
return;
}
else
{
it->second->treeFirst(sts,bar);
num -=sts;
}
it++;
}
}
};
综上所述,全部代码如下:
#include<iostream>
#include<map>
#include<vector>
using namespace std;
char tmps[200];
struct Directory{
string name; //当前目录的名字
map<string,Directory*> children; //用指针是避免复制构造造成的浪费内存
Directory* parent; //以备"CD.."要返回上级目录
int subtreeSize; //以备"SZ"要输出子树大小
vector<string>* tenDescendants;//缓存10个后代
bool updated;//子树有没有发生更新
Directory(string name, Directory* parent)
{
this->name = name;
this->parent = parent;
this->subtreeSize = 1;
updated = false;
children.clear();
tenDescendants = new vector<string>;//这个地方一定要注意定义新的目录的时候要初始化
}
public:
bool addChild(Directory* ch)
{
if(children.find(ch->name) != children.end())
return false;
children[ch->name] = ch;
maintain(ch->subtreeSize);
return true;
}
Directory* getChild(const string &name)
{
auto it = children.find(name);
if(it == children.end())
return nullptr;
return it->second;
}
Directory* mkdir(const 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(const 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(const string &name)
{
if(".."==name)//返回父亲
return this->parent;
return getChild(name);
}
void maintain(int delta)
{//向上维护
updated = true;
subtreeSize += delta;
if(parent != nullptr)
parent->maintain(delta);
}
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());
}
}
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();
treeFirst(5,tenDescendants);
treeLast(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());
}
}
private:
void treeAll(vector<string>* bar)
{
bar->push_back(name);
for(auto &entry:children)
entry.second->treeAll(bar);
}
void treeLast(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->treeLast(num,bar);
return;
}
else
{
it->second->treeLast(sts,bar);
num -=sts;
}
}
bar->push_back(name);
}
void treeFirst(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->treeFirst(num,bar);
return;
}
else
{
it->second->treeFirst(sts,bar);
num -=sts;
}
it++;
}
}
};
struct Command
{
const string CMD_NAMES[7] = {"MKDIR","RM","CD","SZ","LS","TREE","UNDO"};
int type;//命令类型
string arg;//命令的参数
Command(const string &s)
{
for(int i=0;i<7;i++)
{
if(CMD_NAMES[i]==s)
{
this->type = i;
if(i<3) scanf("%s",tmps),arg=tmps;
return;
}
}
}
Directory* tmpDir; //记录刚刚操作涉及的目录节点
};
void solve()
{
int Q;scanf("%d",&Q);
Directory* now = new Directory("root",nullptr);
vector<Command*> cmdList;
cmdList.clear();
while(Q--)
{
scanf("%s",tmps);
Command* cmd = new Command(tmps);
switch(cmd->type)
{
case 0: case 1:
{
cmd->tmpDir = cmd->type==0?now->mkdir(cmd->arg) : now->rm(cmd->arg);
if(cmd->tmpDir == nullptr)printf("ERR\n");
else
{
printf("OK\n");
cmdList.push_back(cmd);
}
break;
}
case 2:
{
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: 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");
}
}
}
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
solve();
if(T!=0) printf("\n");
}
return 0;
}
2.东东学打牌
题目描述
最近,东东沉迷于打牌。所以他找到 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 行,即这次全场人的排名。
题目分析
这个题目最关键的部分就在于多关键字排序,但是不同的牌型之间的比较并不能通过一个简单的参数进行比较,而需要我们去考虑一种方式来得到每一种类型牌的某种权重,需要注意的是我们可以首先得到一副牌的类型,然后在这个类型中所有的牌进行比较大小,这里我采取的解决方法为:对于某一种牌型,我们选取它先比较的内容的数值乘以100,接着比较的内容的数值乘以10,因为牌的大小限制了10倍足够将不同优先级的处理区分开,这样就可以实现权重的定义。
该题全部解决代码如下:
#include<cstdio>
#include<vector>
#include<map>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e5+10;
char ps[15];//用于手牌字符串的读入
struct Pokers
{
int type; //牌的类型
char name[15]; //牌的主人
int weight; //牌的权重
bool operator<(const Pokers& P) const
{
if(type!=P.type)return type>P.type;
else return weight != P.weight ? weight > P.weight : strcmp(name, P.name) < 0;
}
}pokers[N];
void get_type_w(char* ps,int index)
{//获取第i个人手牌的类型和比重
//首先处理为int数组形式
int w = 0,cnt=0;
int num[10];//用于暂时存放转换为数字的手牌->类型和权重
for(int i=0;i<strlen(ps);i++)
{
if(ps[i]>'0' && ps[i]<='9')
{
if(ps[i]=='1')
{
num[cnt++]=10;
i++;
}
else num[cnt++]=ps[i]-'0';
}
else
{
if(ps[i] == 'A')num[cnt++] = 1;
else if(ps[i] == 'J')num[cnt++] = 11;
else if(ps[i] == 'Q')num[cnt++] = 12;
else num[cnt++] = 13;
}
}
sort(num,num+5);
//下面开始判断类型和权重
if(num[0] == 1 && num[1] == 10 && num[2] == 11 && num[3] == 12 && num[4] == 13)pokers[index].type = 8;
else if(num[0]+1 == num[1] && num[1]+1==num[2] && num[2]+1==num[3] && num[3]+1==num[4])
{
pokers[index].type = 7;
pokers[index].weight = num[4];
}
else if((num[0]==num[1] && num[1]==num[2] && num[2]==num[3])||(num[1]==num[2] && num[2]==num[3] && num[3]==num[4]))
{
pokers[index].type = 6;
pokers[index].weight = num[0]==num[3] ? num[0]*100+num[4] : num[1]*100+num[0];
}
else if((num[0]==num[1] && num[1]==num[2] && num[3]==num[4]) || (num[0]==num[1] && num[2]==num[3] && num[3]==num[4]))
{
pokers[index].type = 5;
pokers[index].weight = num[0]==num[2] ? num[0]*100+num[3] :num[2]*100+num[0];
}
else if((num[0]==num[1] && num[1]==num[2]) || (num[2]==num[3] && num[3]==num[4]) || (num[1]==num[2] && num[2]==num[3]))
{
pokers[index].type = 4;
if(num[0]==num[2])pokers[index].weight = num[0]*100+num[3]+num[4];
else if(num[1]==num[3])pokers[index].weight = num[1]*100+num[0]+num[4];
else pokers[index].weight = num[2]*100+num[0]+num[1];
}
else if((num[0]==num[1] && num[2]==num[3]) || (num[1]==num[2] && num[3]==num[4]) || (num[0]==num[1] && num[3]==num[4]))
{
pokers[index].type = 3;
if(num[0]==num[1] && num[2]==num[3])
{
if(num[0]>num[2])pokers[index].weight = num[0]*100*100+num[2]*100+num[4];
else pokers[index].weight = num[2]*100*100+num[0]*100+num[4];
}
else if(num[1]==num[2] && num[3]==num[4])
{
if(num[1]>num[3])pokers[index].weight = num[1]*100*100+num[3]*100+num[0];
else pokers[index].weight = num[3]*100*100+num[1]*100+num[0];
}
else
{
if(num[0]>num[3])pokers[index].weight = num[0]*100*100+num[3]*100+num[2];
else pokers[index].weight = num[3]*100*100+num[0]*100+num[2];
}
}
else if(num[0]==num[1] || num[1]==num[2] || num[2]==num[3] || num[3]==num[4])
{
pokers[index].type = 2;
if(num[0]==num[1])pokers[index].weight = num[0]*100 + num[2] + num[3] + num[4];
else if(num[1]==num[2])pokers[index].weight = num[1]*100 + num[0] + num[3] + num[4];
else if(num[2]==num[3])pokers[index].weight = num[2]*100 + num[0] + num[4] + num[1];//Be carefull !!!
else pokers[index].weight = num[3]*100 + num[0] + num[1] + num[2];
}
else
{
pokers[index].type = 1;
pokers[index].weight = num[0] + num [1] + num[2] + num[3] + num[4];
}
}
int main()
{
int n;
while(~scanf("%d",&n))
{
for(int i=0;i<n;i++)
{
scanf("\n%s%s",pokers[i].name,ps);
get_type_w(ps,i);
}
sort(pokers,pokers + n);
for(int i = 0; i < n; i++)printf("%s\n", pokers[i].name);
}
return 0;
}
3.签到题
题目描述
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
题目分析
这道题目较为简单,主要是锻炼在较短的时间内快速准确的完成CSP T1/T2难度的题目,在一系列板凳的人数中选取最大的加上新来的人便可得到长椅上最多的人,主要是最少的人如何分配,解决的方法分情况讨论,如果待分配的人数比长椅的数目少,则可能的最小值为之前求到的最大值,如果待分配的人数比长椅的数目多,则说明当前的最大值还需要增大,但是为了保证最大值增大的最小,就先让所有的长椅上补全为当前的最大值,然后如果有剩余则平分到各个长椅,无法整除的余数加一即可。
所以这道题目具体的代码如下:
#include<iostream>
using namespace std;
int a[105],maxn = -1,index = -1;
int main()
{
int x,y;
scanf("%d%d",&x,&y);
for(int i=1;i<=x;i++)
{
scanf("%d",&a[i]);
if(maxn<a[i]) maxn = a[i], index = i;
}
int maxm = maxn+y;
for(int i=1;i<=x;i++)
{
int delta = maxn-a[i];
if(y>delta)
y -= delta,a[i] += delta;
else
{
y=0;
break;
}
}
if(y==0) //在保证最大值不变的情况下分配完y
printf("%d ",maxn);
else //a[i]全部更新为相同的值maxn且y还有剩余
printf("%d ",y%x==0?maxn+y/x:maxn+y/x+1);
printf("%d",maxm);//输出最大数
return 0;
}