A-咕咕东的目录管理器

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)的影响

输入

输入文件包含多组测试数据,第一行输入一个整数表示测试数据的组数 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

二、思路与算法

本题的难点在于如何实现UNDO撤销操作,如果没有撤销操作题目会简单很多,但加上撤销操作之后,我们不仅仅需要完成每个操作,还要记录下每个操作以及对应的参数、当时所在的节点,以供后面撤销时候调用。
所以数据结构有两个:Directory和command。Directory是存储结点的结构,command是存储命令的结构。

  • MKDIR:新建子目录,先检查是否已经存在,因为后续有撤销可能,所以在新建子目录的时候要记录下新建的子目录、命令、当前节点等。
  • RM:去掉连着之间的连接,但不会删除某个节点。记录有关命令的信息。
  • CD:进入子目录,记录有关命令的信息,便于后续反向行走。
  • SZ:直接输出当前节点记录的以当前节点为根的子树的大小,不需要记录命令,也不能撤销。
    (必须每个节点都记录,否则每次都要遍历一次子树,时间复杂度过高)
  • LS:输出节点记录的pre和bck,也是可以直接输出,不需要记录命令,无法撤销。
  • UNDO:从记录的命令中取出最新的一条,不同的命令有对应的不同的撤销操作。撤销MKDIR就删除对应的目录,撤销RM就把去掉的连接再加上,重新连接两个节点,撤销CD,就反向行走。记录了这些命令的信息、节点的信息之后,撤销操作的实现就也方便许多。

三、代码实现

#include <iostream>
#include <cstring>
#include <map>
#include <vector>
#include <algorithm>
using namespace std;

struct Directory{
	string name;
	int fa,sz;
	vector<string> pre,bck;//前序、后序遍历5个以内文件 
	bool tag;  //是否更新过 
	map<string,int > mp;
	void init(string na,int father){
		tag=false;	fa=father;	sz=1;
		name=na;	pre.clear();	bck.clear();	mp.clear();
	}
};

struct compand{//因为有undo操作,所以要记录每个操作和对应的参数、当时的当前节点 
	string name,tr;
	int type;
}; 

int T=0;   //数据组数 
int Q=0;   //操作数目 
int cur=0;   //目前最新节点编号,因为需要给每个节点名字和编号对应起来,所以要记录 
int now=0;   //目前所在节点(目前所在文件) 
Directory node[300000];   //所有节点(文件) 
compand cmd;   //目前的操作 
vector<pair<string, pair<int, int> > > v;  //所有已经进行过的操作及其参数、进行时的当前节点标号 

void update(int id,int num)   
{
	while(id!=-1){
		node[id].tag=false;
		node[id].sz+=num;
		id=node[id].fa;
	}
}

void mkdir(){//新建子文件 
	if(node[now].mp.count(cmd.tr)){//已经存在该子文件了
		cout<<"ERR\n";
	}
	else{ 
		node[++cur].init(cmd.tr,now);
 		node[now].mp[cmd.tr]=cur;
		update(now,1);   //需要更新上方所有相关节点! 
		v.push_back(make_pair(cmd.name,make_pair(now,cur)));
		cout<<"OK\n";
	} 
}

void rm(){
	if(!node[now].mp.count(cmd.tr)){   //不存在该子目录,无法删除 
		cout<<"ERR\n"; 
	}
	else{
		int cur1=node[now].mp[cmd.tr];
		update(now,-node[cur1].sz); 
		node[now].mp.erase(node[cur1].name);
		cout<<"OK\n";
		v.push_back(make_pair(cmd.name,make_pair(now,cur1)));
	}
}

void cd(){   //进入子目录 
	if(cmd.tr==".."){   //返回上一级 
		if(node[now].fa==-1){
			cout<<"ERR\n";   //已经在根节点上 
		}
		else{
			v.push_back(make_pair(cmd.name,make_pair(now,node[now].fa)));
			now=node[now].fa;
			cout<<"OK"<<endl;
		}
	}
	else{
		if(!node[now].mp.count(cmd.tr)){   //找不到目录,无法进入 
			cout<<"ERR\n";
		}
		else{ 
			v.push_back(make_pair(cmd.name,make_pair(now,node[now].mp[cmd.tr])));   //添加操作 
			now=node[now].mp[cmd.tr];
			cout<<"OK\n";
		} 
	}
}

void undo(){   //撤销 
	if(v.size()==0) {cout<<"ERR\n";return;}
	auto st=v[v.size()-1];
	v.pop_back();
	int temp=now;
	if(st.first=="MKDIR"){
		cmd.name="RM";
		now=st.second.first;
		cmd.tr=node[st.second.second].name;
		int cur1=node[now].mp[cmd.tr];
		update(now,-node[cur1].sz);
		node[now].mp.erase(node[cur1].name);
		now=temp;
	}
	else if(st.first=="RM"){
		now=st.second.first;
		int cur1=st.second.second;
		update(now,node[cur1].sz);
		node[now].mp[node[cur1].name] = cur1;
		now = temp;
	}
	else{
		now = st.second.first;
	}
	cout<<"OK\n";
}

void ls(){   //输出直接子目录名 
	int tmp=node[now].mp.size();
	if(tmp==0){
		cout<<"EMPTY\n";
		return;
	}
	auto pos=node[now].mp.begin();
	if(tmp>=1&&tmp<=10){
		while(pos!=node[now].mp.end()){
			cout<<pos->first<<"\n";
			pos++;
		}
		return;
	}
	for(int i=0;i<5;i++){
		cout<<pos->first<<"\n";
		pos++;
	}
	cout<<"...\n";
	pos=node[now].mp.end();
	for(int i=0;i<5;i++){pos--;};
	for(int i=0;i<5;i++){
		cout<<pos->first<<"\n";
		pos++;
	}
}

void pushdown(int x);//因为互相都有调用,所以要提前声明 

void pretrack(int x){   //前序遍历 
	node[x].pre.push_back(node[x].name);
	if(node[x].sz==1){
		return;
	}
	if(node[x].sz<=10)   //在10以内,全部输出 
	{
		for(auto c:node[x].mp)
		{
			if(!node[c.second].tag){
				pushdown(c.second);
			} 
			node[x].pre.insert(node[x].pre.end(),node[c.second].pre.begin(), node[c.second].pre.end());
		}
		return;
	}
	int cnt=1;
	for(auto c:node[x].mp) {   //在10以外,输出前后5个 
		if(!node[c.second].tag){
			pushdown(c.second);
		}
		for(auto j:node[c.second].pre) {
			node[x].pre.push_back(j);
			cnt++;
			if(cnt>=5){	break;	}
		}
		if(cnt>=5){	break;	}
	}
}

void bcktrack(int x){   //后序遍历 
	auto it=node[x].mp.end();	it--;
	int cnt=0;   //用于计数 
	while(1) 
	{
		int tmp=it->second;
		if(!node[tmp].tag){	pushdown(tmp);	}
		for(int i=node[tmp].bck.size()-1;i>=0;i--){
			node[x].bck.push_back(node[tmp].bck[i]);
			cnt++;
			if(cnt>=5){   //如果在5个以上 
				reverse(node[x].bck.begin(), node[x].bck.end());  //包含在algorithm中,将所有元素逆序排列 
				break;
			}
		}
		if(cnt>=5){	break;	} 
		if(it==node[x].mp.begin()){	break;	} 
		it--;
	}
}

void pushdown(int id){
	node[id].pre.clear();
	node[id].bck.clear();
	pretrack(id);
	if(node[id].sz>10){bcktrack(id);	}
	else{
		node[id].bck = node[id].pre;
	}
	node[id].tag=true;
}

void tree(){   //输出以当前目录为根的子树的前序遍历结果 
	if(!node[now].tag){pushdown(now);	}
	if(node[now].sz==1){cout<<"EMPTY\n";	}
	else{
		if(node[now].sz>1&&node[now].sz<=10){
			for(int i=0;i<node[now].pre.size();i++){
				cout<<node[now].pre[i]<<endl;
			}		 
		}
		else{
			for(int i=0;i<5;i++){
				cout<<node[now].pre[i]<<endl;
			}
			cout<<"...\n";
			for(int i=5;i>=1;i--){
				cout<<node[now].bck[node[now].bck.size()-i]<<endl;
			}
		}
	}
}

void init() {
	cur=0;	now=0;	v.clear();
	node[0].init("root", -1);
}

int main()
{
	cin>>T;
	for(int i=0;i<T;i++){
		cin>>Q;
		init(); //初始化 
		for(int j=0;j<Q;j++){
			cin>>cmd.name;
			if(cmd.name=="MKDIR"||cmd.name=="RM"||cmd.name=="CD"){
				cin>>cmd.tr;
			}
			if(cmd.name=="MKDIR"){mkdir();	continue;}
			if(cmd.name=="RM"){rm();	continue;}
			if(cmd.name=="CD"){cd();	continue;	}
			if(cmd.name=="SZ"){
				cout<<node[now].sz<<"\n";	continue;
			}
			if(cmd.name=="LS"){ls();	continue;	}
			if(cmd.name=="TREE"){tree();	continue;	}
			if(cmd.name=="UNDO"){undo();	continue;	}
		}
	}
	return 0;
}



四、经验与总结

  1. 本体降低时间复杂度的方法主要就是用空间换时间,每个节点不仅仅记录基本信息,还要记录父结点fa(便于反向行走)、子树大小、子目录前后序pre和bck等,每个操作都update()更新相关节点的信息,最后调用某个操作时,直接使用这些记录好的数据,操作更加方便。
  2. 字符串输出时不要忘记调用c_str()函数。
  3. 在有字符串参与的题目中,可以使用cin和cout,关闭同步流,写起来更方便,更不容易出错。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值