A - 咕咕東的目錄管理器
題目描述
咕咕東的雪梨電腦的作業系統在上個月受到宇宙射線的影響,時不時發生故障,他受不了了,想要寫一個高效易用零bug的作業系統 —— 這工程量太大了,是以他定了一個小目标,從實作一個目錄管理器開始。前些日子,東東的電腦終于因為過度收到宇宙射線的影響而當機,無法寫代碼。他的好友TT正忙着在B站看貓片,另一位好友瑞神正忙着打守望先鋒。現在隻有你能幫助東東!
初始時,咕咕東的硬碟是空的,指令行的目前目錄為根目錄 root。
目錄管理器可以了解為要維護一棵有根樹結構,每個目錄的兒子必須保持字典序。
現在咕咕東可以在指令行下執行以下表格中描述的指令:

Input
輸入檔案包含多組測試資料,第一行輸入一個整數表示測試資料的組數 T (T <= 20);
每組測試資料的第一行輸入一個整數表示該組測試資料的指令總數 Q (Q <= 1e5);
每組測試資料的 2 ~ Q+1 行為具體的操作 (MKDIR、RM 操作總數不超過 5000);
面對資料範圍你要思考的是他們代表的 “指令” 執行的最大可接受複雜度,隻有這樣你才能知道你需要設計的是怎樣複雜度的系統。
Output
每組測試資料的輸出結果間需要輸出一行空行。注意大小寫敏感。
限制
Time limit 6000 ms
Memory limit 1048576 kB
Sample Input
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
Sample Output
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
題解
整個目錄是一棵樹形結構,每個節點包含目前目錄的名字,子目錄的集合(map<子目錄名字,指針>,子目錄是有序的),紙箱上一級目錄的指針,子目錄的數目,緩存十個後代目錄清單(緩存全部(<=10)或前5個和後5個(>10),為懶更新做準備),該目錄下的子清單有沒有更新過的标志(為懶更新做準備,進行删除或者添加子目錄時改為1,更新過後代目錄清單後改為0)。
懶更新,每次運作tree時都重新周遊後代太複雜,會逾時。是以我們采用懶更新的方法,在每次周遊完成後都将此時的後代儲存起來,同時置updated為0,若有操作更新了此節點的後代則改為1,下次調用此節點的tree時,若updated為0則該節點的子樹沒有被更新過,直接輸出即可,updated為1時則需重新周遊。
為了undo友善,在前三種操作中,如果操作成功了,就要将操作類型和改變的節點儲存起來,mkdir的撤銷可以直接調用rm,但是rm的撤銷不可以直接調用mkdir,因為有可能删除的是一個帶子目錄的目錄,需要将子目錄與目錄都加進目錄清單才能變回原來的狀态。是以我們将删除的目錄儲存起來(子目錄仍然挂在目錄下面)。
在for循環中用疊代器周遊數組時,i與it要同時++。
注意subtreeSize是本目錄和所有子目錄的集合。
代碼
#include <iostream>
#include <stdio.h>
#include <string>
#include <string.h>
#include <vector>
#include <map>
using namespace std;
char tmps[20];//輔助輸入的字元數組
struct directory{
string name;//目前目錄的名字
map<string,directory*>children;//孩子目錄
directory*parent;
int subtreeSize;//子樹大小
vector<string>*ten;//緩存0個後代
bool updated;//有沒有被問過
directory(string name,directory*parent){
this->name=name;
this->parent=parent;
this->subtreeSize=1;
ten=new vector<string>;
updated=1;
}
bool addchild(directory*ch){
if(children.find(ch->name)!=children.end()){return 0;}
children[ch->name]=ch;
maintain(+ch->subtreeSize);
return 1;
}
directory* getchild(string name){
auto it=children.find(name);
if(it==children.end()){return NULL;}
else{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){
auto it= children.find(name);
if(it==children.end()){return NULL;}//不存在這個子目錄,無法删除
maintain(-1*it->second->subtreeSize);
directory*temp=it->second;///本來沒有這一步
children.erase(it);
return temp;
}
directory*cd(string name){
if(name==".."){return this->parent;}
return getchild(name);//不存在會傳回來NULL
}
void maintain(int d){
this->updated=1;
subtreeSize+=d;
if(parent!=NULL){
parent->maintain(d);
}
}
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 &i:children)
printf("%s\n",i.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());
it++;
}
}
}
void tree(){
if(subtreeSize==1)printf("EMPTY\n");
else if(subtreeSize<=10){//誤以為+1
if(this->updated){//更新過
ten->clear();
treeAll(ten);
this->updated=0;
}
for(int i=0;i<subtreeSize;i++){
printf("%s\n",ten->at(i).c_str());
}
}else{
if(this->updated){//更新過
ten->clear();
treeFirst(5,ten);
treeLast(5,ten);
this->updated=0;
}
for(int i=0;i<5;i++){
printf("%s\n",ten->at(i).c_str());
}
printf("...\n");
for(int i=9;i>=5;i--){
printf("%s\n",ten->at(i).c_str());
}
}
}
private:
void treeAll(vector<string>*v){
v->push_back(name);
for(auto &i:children){
i.second->treeAll(v);
}
}
void treeFirst(int num,vector<string>*v){
v->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,v);
return;
} else{
it->second->treeFirst(sts,v);錯寫為num
num-=sts;
}
it++;
}
}
void treeLast(int num,vector<string>*v){
int n=children.size();
auto it=children.end();
while(n--){
it--;
int sts=it->second->subtreeSize;//孩子的大小
if(sts>=num){
it->second->treeLast(num,v);
return;
} else{
it->second->treeLast(sts,v);
num-=sts;
}
}
v->push_back(name);
}
};
struct Command{
const string CMD_NAMES[7]={"MKDIR","RM","CD","SZ","LS","TREE","UNDO"};
int type;//指令的類型 0->mkdir 1->rm 2->cd
//3->sz 4->ls 5->tree 6->undo
string arg;//指令參數
directory*tmpdir;//剛剛操作涉及的目錄節點
Command(string s){//構造函數
tmpdir=NULL;
for(int i=0;i<7;i++)if(CMD_NAMES[i]==s){
type=i;
if(type<3){scanf("%s",tmps);arg=tmps;}
return;
}
}
};
vector<Command*>cmdlist;//已經成功的指令
void solve(){//對每一組測試資料
int n; scanf("%d",&n);//每組資料有n行
directory*now=new directory("root",NULL);
vector<Command*>cmdlist;//已經成功的指令
while(n--){
scanf("%s",tmps);
Command*cmd=new Command(tmps);
switch(cmd->type)
{
case 0:{//mkdir
cmd->tmpdir=now->mkdir(cmd->arg);
if(cmd->tmpdir==NULL){printf("ERR\n");}
else{
printf("OK\n");
cmdlist.push_back(cmd);
}
break;
}
case 1:{//rm
cmd->tmpdir=now->rm(cmd->arg);
if(cmd->tmpdir==NULL){printf("ERR\n");}
else{
printf("OK\n");
cmdlist.push_back(cmd);
}
break;
}
case 2:{
//cd
directory*ch=now->cd(cmd->arg);//
if(ch==NULL){printf("ERR\n");}
else{
printf("OK\n");
cmd->tmpdir=now;//将目前目錄存起來,undo直接回這裡
now=ch;//進入新目錄
cmdlist.push_back(cmd);
}
break;
}
case 3://sz
now->sz();break;
case 4://ls
now->ls();break;
case 5://tree
now->tree();break;
case 6:{//undo
bool success=false;
if(!cmdlist.empty()){//一開始這裡是while
cmd=cmdlist.back();cmdlist.pop_back();
switch(cmd->type){
case 0:success=now->rm(cmd->tmpdir->name)!=NULL;break;
case 1:success=now->addchild(cmd->tmpdir)!=NULL;break;
case 2:now=cmd->tmpdir;success=1;break;
}
}
printf(success?"OK\n":"ERR\n");
break;
}
}
}
}
int main(int argc, char** argv) {
int t;scanf("%d",&t);
while(t--){solve();}
return 0;
}