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
思路
按照学长的PPT来。。。
Step0. 无从下手
经验:如果一下子不习惯/无法从整个大局进行考虑,自顶向下地设计整个程序框架,不妨就从程序的入口开始。先写好程序入口,写好大致需要的变量。
Step1. 思考封装
考虑“有什么”和“做什么”。
比如刚刚我们有一个命令字符串“tmps”,它即代表一条命令。
考虑到一条命令不单有命令形式,还有命令参数,比如“MKDIR s”,等会我们肯定还要进行参数的分离,同类信息最好内聚,所以 —— 封装之!
Step2. 树形结构
维护一棵目录树是实现这道题的必然需求 —— 如何维护一棵树?
题目要求一个目录能够根据子目录的名字取到它的子目录
根据键取值, Key-Value ? 所以是 map ?
对,如果不要求字典序,用底层是哈希的 unordered_map, O(1) 的复杂度,将绝杀,可惜用不得
所以要用 map<string,目录>,它可以根据 key 也就是 string 在内部进行排序。这样每次可以 log 级别复杂度取到子目录
Step3. 解题框架
要开始写具体实现的时候,你发现,对于每条指令要有一个执行对象,那就是当前目录 “now”
如此,解题框架就如下:
即我们在 Directory 结构体里面写各种具体的实现
同时你发现两个问题,
第一,你要设计“返回值”告知某条命令的执行的结果成功/失败
第二,你想起来“UNDO”没法封装在某一个 Directory 内部,它是隶属于当前测试数据环境的
所以,对于执行成功的命令,你还要存起来,以备“UNDO”
回去题面分析一下可以 UNDO 的命令有关键
必须是 MKDIR、 RM、 CD 三种之一
必须已执行成功
如果 UNDO
MKDIR 的 UNDO 就是删掉,那么删谁?
RM 的 UNDO 就是加回去,那么加谁?
CD 的 UNDO 就是退回去,退到哪?
—— 需要保存每条指令执行的执行结果,保存到 -> struct Command{…}
Step5. 注入灵魂
直到要实现 TREE 命令的时候,你发现它是最难的
因为在后代节点数量大于 10 时,要开始前序遍历和后序遍历
并且对复杂度有要求!
TREE 的命令条数最多约为 1e5,此时整个树最多 5000 个节点
20 * 1e5 * 5000,你发现可能会 TLE
—— 你需要一个较好的实现
答案是缓存(懒更新),节点数远少于 TREE 操作数,指不定还有重复询
问,对于目录相同期间问过的相同问题,理应只有一次是计算过程
总结
补完前话中没有说完的部分
题意分析之利用时限计算复杂度设计数据结构:
设计树形数据结构时比起 O(n) 用 map 可以 O(logn) 来找子目录;
分析时限,发现 TREE 操作暴力实现是不可接受的,于是我们使用了记忆化(缓存)技巧。
解题框架设计之从何入手:
如果不知从何入手,就从 int main() 开始先写几行;
然后对于一个 “功能” 不要一上来就写细节,而是假装已经封装写好了,从而先避免细节,来关注整体设计。
面向对象中之封装不要过于拘束:
printf 究竟在类内还是类外,我们选择了都要
类似的抉择还会有的,坚持高内聚的架构,还是肆意放飞,你写代码舒服就好的。毕竟现在只是为了解决一道题,以后工程架构时这些经历会帮助你的。 (只要坚持不妥协)
代码
#include<iostream>
#include<map>
#include<vector>
#include<string.h>
using namespace std;
struct Directory {
string name;
map<string,Directory*> children;
Directory* parent;
bool updated;
int subtreeSize;
vector<string> *tenDes;
Directory(string tname, Directory* tparent) {
this->name = tname;
this->parent = tparent;
this->subtreeSize = 1;
tenDes = new vector<string>;
}
Directory* getChild(string name) {
map<string,Directory*>::iterator it = children.find(name);
if(it == children.end()) {
return NULL;
}
return it->second;
}
Directory* mkdir(string name) {
if(children.find(name)!=children.end()) {
return NULL;
}
Directory* ch = new Directory(name,this);
children[name] = ch;
maintain(+1);
return ch;
}
Directory* rm(string name) {
map<string,Directory*>::iterator it = children.find(name);
if(it == children.end()) {
return NULL;
}
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 maintain(int delta) {
updated = true;
subtreeSize += delta;
if(parent!=NULL) {
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) {
map<string,Directory*>::iterator it = children.begin();
for(int i=0; i<sz; i++,it++) {
printf("%s\n",it->first.c_str());
}
} else {
map<string,Directory*>::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 treeAll(vector<string>* bar) {
bar->push_back(name);
map<string,Directory*>::iterator it = children.begin();
for(; it!=children.end(); it++) {
it->second->treeAll(bar);
}
}
void treeLastSome(int num, vector<string>* bar) {
int n = children.size();
map<string,Directory*>::iterator 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);
}
void treeFirstSome(int num, vector<string>* bar) {
bar->push_back(name);
if(--num == 0) return;
int n = children.size();
map<string,Directory*>::iterator it = children.begin();
while(n--) {
int sts = it->second->subtreeSize;
if(sts>=num) {
it->second->treeFirstSome(num,bar);
return;
} else {
it->second->treeLastSome(sts,bar);
num -= sts;
}
it++;
}
}
void tree() {
if(subtreeSize == 1) printf("EMPTY\n");
else if(subtreeSize <= 10) {
if(this->updated) {
tenDes->clear();
treeAll(tenDes);
this->updated = false;
}
for(int i=0; i<subtreeSize; i++) printf("%s\n",tenDes->at(i).c_str());
} else {
if(this->updated) {
tenDes->clear();
treeFirstSome(5,tenDes);
treeLastSome(5,tenDes);
this->updated = false;
}
for(int i=0; i<5; i++) {
printf("%s\n");
}
}
}
};
string cmdnames[14] = {"mkdir","rm","cd","sz","ls","tree","undo","MKDIR","RM","CD","SZ","LS","TREE","UNDO"};
struct command {
int type;
string arg;
Directory* tmpDir;
command(string s) {
int i = 0;
for(; i<14; i++) {
if(cmdnames[i]==s) {
type = i%7;
if(type<3) cin>>arg;
}
}
}
};
void solve() {
string tmp;
int n;
cin>>n;
Directory* now = new Directory("root",NULL);
vector<command*> cmdList;
while(n) {
n--;
cin>>tmp;
command* cmd = new command(tmp);
switch(cmd->type) {
case 0:
cmd->tmpDir = now->mkdir(cmd->arg);
if(cmd->tmpDir == NULL) printf("ERR\n");
else {
printf("OK\n");
cmdList.push_back(cmd);
}
break;
case 1:
cmd->tmpDir = now->rm(cmd->arg);
if(cmd->tmpDir == NULL) printf("ERR\n");
else {
printf("OK\n");
cmdList.push_back(cmd);
}
break;
case 2: {
Directory* ch = now->cd(cmd->arg);
if(ch == NULL) 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)!=NULL);
break;
case 1:
success = (now->addChild(cmd->tmpDir));
break;
case 2:
now = cmd->tmpDir;
success = true;
break;
}
}
printf(success?"OK\n":"ERR\n");
break;
}
}
}
}
int main() {
// freopen("ou1.txt","w",stdout);
int t;
cin>>t;
while(t){
t--;
solve();
}
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 张牌不同,其值不同。下面依次列举了这手牌的形成规则:
- 大牌:这手牌不符合下面任一个形成规则。如果 α 和 β 都是大牌,那么定义它们的大小为组成这手牌的 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
思路
这道题主要是对于扑克牌大小的确定,所以只需要对哪些情况进行判定即可,将扑克牌做成一个结构体,对各种情况进行判定即可。
struct poke {
int p[5];
string name;
map<int,int> cnt;
set<int> st;
int num;}
但是,我们可以注意到,在每一个-判断中会有判断子条件,所以我们可以增加判断条件,对于上一个条件相等的我们可以判断下一个条件依次进行:
int first,second,third,forth;
结构体代码如下:
struct poke {
int p[5];
string name;
map<int,int> cnt;
set<int> st;
int num;
int first,second,third,forth;
void init(string n,string s) {
first=0;
second=0;
third=0;
forth = 0;
cnt.clear();
st.clear();
num = 0;
this->name = n;
int length = s.length();
// cout<<length<<endl;
int ci = 0;
for(int i=0; i<length; i++) {
char tmp = s.c_str()[i];
if(tmp==65) {
p[ci] = 1;
} else if(tmp==74) {
p[ci] = 11;
} else if(tmp==75) {
p[ci] = 13;
} else if(tmp==81) {
p[ci] = 12;
} else if(tmp==49) {
i++;
p[ci] = 10;
} else {
p[ci] = tmp - 48;
}
if(cnt.count(p[ci])!=0) cnt[p[ci]]++;
else {
cnt[p[ci]] = 1;
}
st.insert(p[ci]);
ci++;
}
sort(p+0,p+5);
if(p[0]==1&&p[1]==10&&p[2]==11&&p[3]==12&&p[4]==13) {
num = 8;
} else {
int i = 0;
for(i=0; i<4; i++) {
if(p[i]+1!=p[i+1]) {
break;
}
}
if(st.size()==1) {
second = p[0];
num = 6;
}
else if(i==4) num = 7;
else {
if(st.size()==2) {
set<int>::iterator it = st.begin();
for(; it!=st.end(); it++) {
if(cnt[*it]==4) break;
}
if(it!=st.end()) {
second = *it;
num = 6;
} else {
num = 5;
}
} else if(st.size()==3) {
set<int>::iterator it = st.begin();
for(; it!=st.end(); it++) {
if(cnt[*it]==3) break;
}
if(it!=st.end()) num = 4;
else {
num = 3;
}
} else if(st.size()==4) num = 2;
else {
num = 1;
}
}
}
}
void out() {
cout<<name<<":";
for(int i=0; i<5; i++) {
cout<<p[i]<<" ";
}
cout<<endl;
cout<<num<<endl;
}
void update() {
first = num;
if(num==8) {
second = 0;
third = 0;
} else if(num == 7) {
second = p[4];
third = 0;
} else if(num == 6) {
set<int>::iterator it = st.begin();
for(; it!=st.end(); it++) {
if(cnt[*it]==1){
third = *it;
break;
}
}
} else if(num == 5) {
set<int>::iterator it = st.begin();
for(; it!=st.end(); it++) {
if(cnt[*it]==3) second = *it;
if(cnt[*it]==2) third = *it;
}
} else if(num==4) {
set<int>::iterator it = st.begin();
third = 0;
for(; it!=st.end(); it++) {
if(cnt[*it]==3) second = *it;
if(cnt[*it]==1) third += (*it);
}
} else if(num==3) {
set<int>::iterator it = st.begin();
second = 0;
third = 20;
for(; it!=st.end(); it++) {
if(cnt[*it]==2) {
second = max(second,*it);
third += min(third,*it);
} else {
forth = *it;
}
}
} else if(num==2) {
set<int>::iterator it = st.begin();
second = 0;
for(; it!=st.end(); it++) {
if(cnt[*it]==2) {
second = *it;
} else {
third += (*it);
}
}
} else {
for(int i=0; i<5; i++) {
second += (p[i]);
}
}
}
bool operator < (poke b) const {
if(first!=b.first) return first<b.first;
else {
if(second!=b.second) return second<b.second;
else {
if(third!=b.third) return third<b.third;
else {
if(forth!=b.forth) return forth<b.forth;
else {
return name>b.name;
}
}
}
}
}
};
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。
代码
#include<iostream>
#include<cmath>
using namespace std;
int a[2000];
int main(){
int x,y;
cin>>x>>y;
int m = 0;
for(int i=0;i<x;i++){
scanf("%d",&a[i]);
m = max(m,a[i]);
}
int ans2 = m + y;
int s = 0;
for(int i=0;i<x;i++){
s += (m-a[i]);
}
int ans1 = 0;
if(y<=s) ans1 = m;
else{
ans1 = m + ceil((y-s)*1.0/x);
}
printf("%d %d",ans1,ans2);
return 0;
}