SDU_week9_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
hint

英文原版题面:

在这里插入图片描述

题目分析

这是一道巨型模拟题,对于这样一个复杂的模拟题,我们应该首先从大局考虑,自顶向下地设计整个框架,先写出函数的接口,内部实现最后再写。首先一个基本的框架可以写出来:

#include <bits/stdc++.h>
using namespace std;

void solve()
{
    Directory* now = new Directory("root",nullptr);//根
    vector<Command*> cmdList;//成功操作的命令,用于UNDO
    cmdList.clear();
    cin>>Q;
    while (Q--)
    {
        cin>>onecmd;
        Command* cmd= new Command(onecmd);
        switch (cmd->type)
        {
            case 0: 
            case 1:
            case 2:
            case 3: 
            case 4: 
            case 5: 
            case 6: 
 }
int main()
{
    // _;
    cmd_map["MKDIR"]=0;
    cmd_map["RM"]=1;
    cmd_map["CD"]=2;
    cmd_map["SZ"]=3;
    cmd_map["LS"]=4;
    cmd_map["TREE"]=5;
    cmd_map["UNDO"]=6;

    cin>>T;
    while(T--)
    {
        solve();
        if(T!=0) cout<<endl;
    }
    return 0;

然后我们需要考虑命令有什么,需要我们做什么。由于有的命令有命令参数,比如"MKDIR s",所以我们需要进行参数的分离,同类的信息进行内聚,我们就需要封装Command:

struct Command
{
    int type;//cmd类型
    string arg;//参数,即目录s
    Directory* tmpDir;//操作位置,失败则为nullptr
    Command(string &onecmd)
    {
        tmpDir=nullptr;
        type=cmd_map[onecmd];
        if(type<3)  cin>>arg; //前三种有参数
    }
};

然后我们需要考虑的是一棵目录树,这是一个必然的需求。那我们使用什么来维护这棵树?

  • 数据结构上的二叉树?
  • 图论上的多叉树?

我们可以注意到,题目要求根据子目录的名字得到子目录,因此我们可以使用map来处理,使用map<string,目录>,这可以根据key值在内部进行字典序排序,这样每次取到子目录的时间复杂度都是log级别的。
所以我们最终的树结构是这样的:

struct Directory
{
    string name;//目录名
    map<string,Directory*> children;//子目录,用指针为了避免复制构造造成的内存浪费
    Directory* parent;//父节点,用于cd ..
    int subtreeSize;//用于sz
    vector<string>* tenDescendants;//当前节点的5+5后代
    bool updated;//记录子树是否变动过
    Directory(string _name,Directory* _parent)
    {
        this->name=_name;
        this->parent=_parent;
        this->subtreeSize=1;//自己
        this->updated=true;//注意
        this->tenDescendants=new vector<string>;
    }

    Directory* getChild(string &_name);//取得子目录并返回,不存在就返回空指针
    Directory* mkdir(string &_name); //创建子目录并返回,创建失败返回空指针
    Directory* rm(string &_name);//删除子目录并返回,删除失败则返回空指针
    Directory* cd(string &_name);//进入子目录或上层目录
    Directory* addChild(Directory* ch);//加入子目录并返回成功与否,用于UNDO
    void maintain(int delta);//向上维护子树的大小,便于实现"SZ"
    void sz();//目录大小
    void ls();//当前目录情况
    void tree();//以当前目录为根的子树的前序遍历结果
    void treeAll(vector<string>* tenBuffer);//更新全部tenDescendants
    void treeFirstFive(int num,vector<string>* tenBuffer);//前序遍历并加入首要的num个后代
    void treeLastFive(int num,vector<string>* tenBuffer);//后序遍历并加入首要的num个后代
};

后代节点一旦大于10,就要进行前序和后序的便利,并且观察数据范围我们会知道,如果每一次查询都进行遍历的话,绝对会超时。
因此我们应该做到缓存,也就是懒更新,当节点数远小于TREE操作数量,对于目录相同期间问过的相同问题,理应只有一次计算过程。

代码

//Directory数量会小于Command数量,因为每个操作都会有一条Command
//所有点操作都是在当前目录进行
#define _ ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include <bits/stdc++.h>
using namespace std;

map<string,int>cmd_map;//命令对应数字

int T,Q;//T组数据,每组Q条
string onecmd;//每一条指令非参数部分

struct Directory
{
    string name;//目录名
    map<string,Directory*> children;//子目录,用指针为了避免复制构造造成的内存浪费
    Directory* parent;//父节点,用于cd ..
    int subtreeSize;//用于sz
    vector<string>* tenDescendants;//当前节点的5+5后代
    bool updated;//记录子树是否变动过
    Directory(string _name,Directory* _parent)
    {
        this->name=_name;
        this->parent=_parent;
        this->subtreeSize=1;//自己
        this->updated=true;//注意
        this->tenDescendants=new vector<string>;
    }

    Directory* getChild(string &_name);//取得子目录并返回,不存在就返回空指针
    Directory* mkdir(string &_name); //创建子目录并返回,创建失败返回空指针
    Directory* rm(string &_name);//删除子目录并返回,删除失败则返回空指针
    Directory* cd(string &_name);//进入子目录或上层目录
    Directory* addChild(Directory* ch);//加入子目录并返回成功与否,用于UNDO
    void maintain(int delta);//向上维护子树的大小,便于实现"SZ"
    void sz();//目录大小
    void ls();//当前目录情况
    void tree();//以当前目录为根的子树的前序遍历结果
    void treeAll(vector<string>* tenBuffer);//更新全部tenDescendants
    void treeFirstFive(int num,vector<string>* tenBuffer);//前序遍历并加入首要的num个后代
    void treeLastFive(int num,vector<string>* tenBuffer);//后序遍历并加入首要的num个后代
};

Directory* Directory::getChild(string &_name)
{
    auto it =children.find(_name);
    if(it==children.end()) return nullptr;
    return it->second;
}

Directory* 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* Directory::rm(string &_name)
{
    auto it=children.find(_name);
    if(it==children.end()) return nullptr;
    maintain(-1*(it->second->subtreeSize));//从该点向上每棵树的size值减去其子树size值
    Directory* temptt = it->second;
    children.erase(it);
    return temptt;
}

Directory* Directory::cd(string &_name)
{
    if(_name=="..")return this->parent;
    return getChild(_name);
}

Directory* Directory::addChild(Directory* ch)
{
    if(children.find(ch->name)!=children.end()) return nullptr;//已经存在
    children[ch->name]=ch;
    maintain(ch->subtreeSize);//从该点向上每棵树的size值加上其子树size值
    return ch;
}

void Directory::maintain(int delta)
{
    updated=true;
    subtreeSize+=delta;
    if(parent!=nullptr)parent->maintain(delta);
}

void Directory::sz()
{
    cout<<this->subtreeSize<<endl;
}

void Directory::ls()
{
    int sz=children.size();
    if(sz==0) cout<<"EMPTY"<<endl;
    else if(sz<=10) for(auto &entry:children) cout<<entry.first<<endl;
    else
    {
        auto it=children.begin();
        for(int i=0;i<5;i++,it++) cout<<it->first<<endl;
        cout<<"..."<<endl;
        it=children.end();
        for (int i=0; i<5; i++) it--;
        for (int i=0; i<5; i++,it++) cout<<it->first<<endl;
    }
}

void Directory::tree()
{
    if(subtreeSize==1) cout<<"EMPTY"<<endl;
    else if(subtreeSize<=10)
    {
        if(this->updated)
        {
            tenDescendants->clear();
            treeAll(tenDescendants);
            this->updated=false;
        }
        int sz = tenDescendants->size();
        for(int i=0;i<sz;i++)cout<<tenDescendants->at(i)<<endl;
    }
    else if(subtreeSize>10)
    {
        if(this->updated)
        {
            tenDescendants->clear();
            treeFirstFive(5,tenDescendants);//根左右,顺序为前num个
            treeLastFive(5,tenDescendants);//右左根,逆序为后num个
            this->updated=false;
        }
        for(int i=0;i<5;i++)cout<<tenDescendants->at(i)<<endl;
        cout<<"..."<<endl;
        for(int i=9;i>=5;i--)cout<<tenDescendants->at(i)<<endl;
    }
    
}

void Directory::treeAll(vector<string>* tenBuffer)
{
    //更新所有tenDescendants
    tenBuffer->push_back(this->name);
    for (auto &entry: children) entry.second->treeAll(tenBuffer);
}

void Directory::treeFirstFive(int num,vector<string>* tenBuffer) 
{//更新前num个tenDescendants
    tenBuffer->push_back(this->name);
    if(--num==0) return;
    int n=children.size();
    auto it=children.begin();
    for(int i=0;i<n;i++,it++)
    {
        int sts=it->second->subtreeSize;//子树大小
        if(sts>=num)
        {//不能完全遍历
            it->second->treeFirstFive(num,tenBuffer);
            return;
        }
        else 
        {//能完全遍历,则遍历num的一部分
            it->second->treeFirstFive(sts,tenBuffer);
            num-=sts;
        }
    }
}

void Directory::treeLastFive(int num,vector<string>* tenBuffer) 
{//更新后num个更新所有tenDescendants
    int n=children.size();
    auto it=children.end();//最后一个的后一个
    while(n--)
    {
        it--;
        int sts=it->second->subtreeSize;
        if(sts>=num)
        {//不能完全遍历
            it->second->treeLastFive(num,tenBuffer);
            return;
        }
        else 
        {//能完全遍历,则遍历num的一部分
            it->second->treeLastFive(sts,tenBuffer);
            num-=sts;
        }
    }
    tenBuffer->push_back(this->name);
}

struct Command
{
    int type;//cmd类型
    string arg;//参数,即目录s
    Directory* tmpDir;//操作位置,失败则为nullptr
    Command(string &onecmd)
    {
        tmpDir=nullptr;
        type=cmd_map[onecmd];
        if(type<3)  cin>>arg; //前三种有参数
    }
};

void solve()
{
    Directory* now = new Directory("root",nullptr);//根
    vector<Command*> cmdList;//成功操作的命令,用于UNDO
    cmdList.clear();
    cin>>Q;
    while (Q--)
    {
        cin>>onecmd;
        Command* cmd= new Command(onecmd);
        switch (cmd->type)
        {
            case 0: 
            {
                cmd->tmpDir=now->mkdir(cmd->arg);
                if(cmd->tmpDir==nullptr)cout<<"ERR"<<endl;
                else 
                {
                    cout<<"OK"<<endl;
                    cmdList.push_back(cmd);
                }
                break;
            }
            case 1:
            {
                cmd->tmpDir=now->rm(cmd->arg);
                if(cmd->tmpDir==nullptr)cout<<"ERR"<<endl;
                else
                {
                    cout<<"OK"<<endl ;
                    cmdList.push_back(cmd);
                }
                break;    
            }
            case 2:
            {
                Directory*te = now->cd(cmd->arg);
                if(te==nullptr)cout<<"ERR"<<endl;
                else
                {
                    cout<<"OK"<<endl;
                    cmd->tmpDir=now;
                    now=te;
                    cmdList.push_back(cmd);
                } 
                break;
            }
            case 3: now->sz();break;
            case 4: now->ls();break;
            case 5: now->tree();break;
            case 6: 
            {//UNDO
                bool success=false;//UODO执行成功与否
                if(!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;
                    }
                }
                if(success) cout<<"OK"<<endl;
                else cout<<"ERR"<<endl;
            }
        }
    }
    
}

int main()
{
    // _;
    cmd_map["MKDIR"]=0;
    cmd_map["RM"]=1;
    cmd_map["CD"]=2;
    cmd_map["SZ"]=3;
    cmd_map["LS"]=4;
    cmd_map["TREE"]=5;
    cmd_map["UNDO"]=6;

    cin>>T;
    while(T--)
    {
        solve();
        if(T!=0) cout<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值