题目描述
Input
Output
每组测试数据的输出结果间需要输出一行空行。注意大小写敏感。
解题思路
以封装的思想来实现每个小功能,由于有undo的操作,因此对于mkdir、rm、cd三种操作需要记录每一次操作的过程;
建立一个目录相关的结构体,其中包括文件名,当前文件的子目录的map,父节点以及当前文件下子树(目录)的规模,当前目录下先序遍历、后序遍历的几个文件名称以及一个标记当前目录是否被更新过的标识量;
对每个命令挨个实现:
- mkdir 在当前目录下创建一个新节点,并建立map的连接,同时将这种联系以pair的形式存入到一个vector容器中,并将mkdir操作指令以pair的形式存入到另一个记录操作的vector中,方便后边进行undo操作;
- rm 删除两个节点之间的关系,同时将操作指令和删除对象都分别存入到相应的vector中,方便进行undo操作;
- cd 根据要求修改当前节点now的值,同时记录相应的操作指令和操作对象;
- sz 如果按照朴素的方式来实现,其复杂度会很高(201e55000=1e10),因此考虑,直接添加一个变量sz在结构体中,用于记录每个节点的子树规模,而这个值只有在mkdir和rm时才会发生变化,因此可以在mkdir和rm操作时,就更新一次这个sz,这样在sz操作时,直接返回当前节点now的sz值即可;
- ls 直接根据当前节点now的子目录的数目,从map中按要求遍历输出即可;
- undo 从操作vector的最后中取出一条操作指令,然后再进行反操作即可,即:之前是mkdir则进行rm,之前是rm则进行mkdir,相应的操作对象都是可以从另一个vector中取得的,而cd的逆指令,只需回到当前节点的父节点即可,而这个也是在目录结构中定义记录了的;
- tree 与sz类似,直接暴力求解会超时,而这一操作又无法像sz一样将所有信息全部记录在结构体中,因此考虑懒更新的方式,只对那些没更新过的节点进行一次操作,可以保证时间和空间的复杂度不会太高。
实现代码
#include<iostream>
#include<string>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;
struct Dict
{//目录节点
string name;
map<string,int> mp;
int fa,sz;
vector<string> pre,bck;
bool tag;
};
Dict node[500010];
map<string,int> opr; //将操作的字符形式转为数值形式
int cnt,now; //cnt是当前已有的目录节点个数,now是当前目录节点
vector<pair<string,pair<int,int> > > v; //用于记录已经实现的mkdir、re、cd操作名和操作目录节点
void update(int id,int num)
{
while(id!=-1)
{
node[id].tag=false;
node[id].sz+=num;
id=node[id].fa;
}
}
void mkdir(string s)
{
if(node[now].mp.count(s))
{//说明当前目录下已经有s
cout<<"ERR"<<endl;
return;
}
cnt++;
node[now].mp.insert(pair<string,int>(s,cnt));
node[cnt].name=s;
node[cnt].fa=now;
node[cnt].sz=1;
node[cnt].mp.clear();
node[cnt].pre.clear();
node[cnt].bck.clear();
node[cnt].tag=false;
update(now,1);
v.push_back(make_pair("MKDIR",make_pair(now,cnt)));
cout<<"OK"<<endl;
}
void rm(string s)
{
if(!node[now].mp.count(s))
{//当前目录下没有s子目录
cout<<"ERR"<<endl;
return;
}
int d=node[now].mp[s];
node[now].mp.erase(s);
update(now,(-1)*node[d].sz);
v.push_back(make_pair("RM",make_pair(now,d)));
cout<<"OK"<<endl;
}
void cd(string s)
{
if(s=="..")
{//返回上级目录
if(node[now].fa==-1)
{//根目录无法返回上级目录
cout<<"ERR"<<endl;
return;
}
v.push_back(make_pair("CD",make_pair(now,node[now].fa)));
now=node[now].fa;
}
else
{//只能进入子目录
if(!node[now].mp.count(s))
{//当前目录下没有s子目录
cout<<"ERR"<<endl;
return;
}
v.push_back(make_pair("CD",make_pair(now,node[now].mp[s])));
now=node[now].mp[s];
}
cout<<"OK"<<endl;
}
void sz()
{
cout<<node[now].sz<<endl;
}
void ls()
{
if(node[now].mp.size()==0)
{
cout<<"EMPTY"<<endl;
return;
}
map<string,int>::iterator iter=node[now].mp.begin();
if(node[now].mp.size()<=10)
{
for(;iter!=node[now].mp.end();++iter)
cout<<iter->first<<endl;
}
else
{
for(int i=0;i<5;++i,++iter)
cout<<iter->first<<endl;
cout<<"..."<<endl;
iter=node[now].mp.end();
for(int i=0;i<5;++i)
iter--;
while(iter!=node[now].mp.end())
{
cout<<iter->first<<endl;
iter++;
}
}
}
void undo()
{
if(v.size()==0)
{
cout<<"ERR"<<endl;
return;
}
pair<string,pair<int,int> > e=v[v.size()-1];
v.pop_back();
int tmp=now;
if(e.first=="MKDIR")
{
now=e.second.first;
int d=e.second.second;
update(now,(-1)*node[d].sz);
node[now].mp.erase(node[d].name);
now=tmp;
}
else if(e.first=="RM")
{
now=e.second.first;
int d=e.second.second;
update(now,node[d].sz);
node[now].mp.insert(pair<string,int>(node[d].name,d));
now=tmp;
}
else
now=e.second.first;
cout<<"OK"<<endl;
}
void pushdown(int id); //声明
void pretrack(int id)
{
node[id].pre.push_back(node[id].name);
if(node[id].sz==1)
return;
map<string,int>::iterator iter=node[id].mp.begin();
if(node[id].sz<=10)
{
for(;iter!=node[id].mp.end();++iter)
{
if(!node[iter->second].tag)
pushdown(iter->second);
node[id].pre.insert(node[id].pre.end(),node[iter->second].pre.begin(),node[iter->second].pre.end());
}
return;
}
int t=1;
for(;iter!=node[id].mp.end();++iter)
{
if(!node[iter->second].tag)
pushdown(iter->second);
for(int j=0;j<node[iter->second].pre.size();++j)
{
node[id].pre.push_back(node[iter->second].pre[j]);
++t;
if(t>=5)
break;
}
if(t>=5)
break;
}
}
void bcktrack(int id)
{
int t=0;
map<string,int>::iterator iter=node[id].mp.end();
--iter;
for(;;--iter)
{
if(!node[iter->second].tag)
pushdown(iter->second);
int d=iter->second;
for(int i=node[d].bck.size() - 1;i>=0;--i)
{
node[id].bck.push_back(node[d].bck[i]);
++t;
if(t>=5)
{
reverse(node[id].bck.begin(), node[id].bck.end());
break;
}
}
if(t>=5)
break;
if(iter==node[id].mp.begin())
break;
}
}
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"<<endl;
else if(node[now].sz>10)
{
for(int i=0;i<5;++i)
cout<<node[now].pre[i]<<endl;
cout<<"..."<<endl;
for(int i=5;i>=1;--i)
cout<<node[now].bck[node[now].bck.size()-i]<<endl;
}
else
{
for(int i=0;i<node[now].pre.size();++i)
cout<<node[now].pre[i]<<endl;
}
}
void opr_init()
{
opr.insert(pair<string,int>("MKDIR",0));
opr.insert(pair<string,int>("RM",1));
opr.insert(pair<string,int>("CD",2));
opr.insert(pair<string,int>("SZ",3));
opr.insert(pair<string,int>("LS",4));
opr.insert(pair<string,int>("TREE",5));
opr.insert(pair<string,int>("UNDO",6));
}
void init()
{
cnt=0;
now=0;
node[now].name="root";
node[now].fa=-1;
node[now].sz=1;
node[now].tag=false;
node[now].mp.clear();
node[now].pre.clear();
node[now].bck.clear();
v.clear();
}
int main()
{
opr_init();
int T;
cin>>T;
while(T--)
{
int Q;
cin>>Q;
init();
for(int i=0;i<Q;++i)
{
string o;
cin>>o;
if(opr[o]==0)
{//创建子目录
string name;
cin>>name;
mkdir(name);
continue;
}
if(opr[o]==1)
{//删除子目录
string name;
cin>>name;
rm(name);
continue;
}
if(opr[o]==2)
{//进入子目录
string name;
cin>>name;
cd(name);
continue;
}
if(opr[o]==3)
{//当前目录大小
sz();
continue;
}
if(opr[o]==4)
{//当前目录的直接子目录名
ls();
continue;
}
if(opr[o]==5)
{//以当前目录为根的子树的前序遍历结果
tree();
continue;
}
if(opr[o]==6)
{//撤销
undo();
continue;
}
}
}
return 0;
}
总结
这道题的难点,我认为有以下几点:
- 学会使用结构化的代码和封装的思想来整理整个思路;
- undo操作的处理方式;
- sz的查询方法;
- tree的查询方法;
这道题对我而言难度还是很大的,更多都是照着助教学长给的代码来写,而其中tree的处理办法还是没能完全掌握,只能说是看懂了代码所写,但自己来写估计还是无法完成,总而言之,这道题还是一道值得多多回顾反思的一道题。