CSP-大模拟的求解方法和实战总结

CSP-聊聊大模拟

大模拟的求解思维

大模拟题,也就是复杂模拟题,是ACM比赛和程序设计中不可或缺的题目类型,同时也是CSP中T3的固定模式题目。这种题目虽然对具体算法的要求不高,但是由于其题目情景设置复杂,数据结构种类繁杂,且由于其题目庞杂可能造成理解或认知上的障碍,很容易在做的时候产生退避的心理导致题目求解未果。因此面对大模拟,建立一套有效的分析问题,建立思路,实现需求的方法论是至关重要的。在正式开始引入题目之前,先谈谈大模拟的求解思路和规划方法论。

题目简述

咕咕东的雪梨电脑的操作系统在上个月受到宇宙射线的影响,时不时发生故障,他受不了了,想要写一个高效易用零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”

input及输入样例

输入文件包含多组测试数据,第一行输入一个整数表示测试数据的组数 T (T <= 20);
每组测试数据的第一行输入一个整数表示该组测试数据的命令总数 Q (Q <= 1e5);
每组测试数据的 2 ~ Q+1 行为具体的操作 (MKDIR、RM 操作总数不超过 5000);
输入样例:

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

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

框架思路概述

根据对上述的题意进行分析理解,我们可以得出题目中的文件系统组织形态如下图所示:
在这里插入图片描述结构特点是一个父节点有多个字节点,整个系统的祖先节点是root节点。
再对要求的操作进行剖析,可以将操作分为三个种类,用如下的图来表示这种关系比较直观:
1、MKDIR\RM\CD三种操作会对子目录产生修改或改变当前目录的位置。
2、SZ\LS\TREE三种操作是在该目录的位置下做相关的查看操作,但并不会对目录和位置有修改。
3、UNDO不是一种固定的操作,而是一个相对于前一步操作而进行的撤销。要注意的是,UNDO只能作用于1类操作,并做上一个1类操作的反操作(MKDIR和RM反操作,CD和退回父目录反操作)。如果上一个1类反操作为空,则操作失败。
在这里插入图片描述分析完这样的数据组织方式和操作类型,可以写出每个节点的数据结构组成:

struct Dictionary
{
   
	string dic_name;//该节点的文件名
	map<string, Dictionary*> child;//孩子节点的map
	Dictionary* father;//父亲节点指针
	int child_num;//孩子节点的数量(包括这个点)
}

使用这种数据结构可以通过father指针访问父亲节点,通过map child根据孩子姓名为key查到的孩子指针访问孩子节点,同时利用child_num统计孩子的数量,花上一定的耐心就可以实现操作1-操作5。剩下的TREE和UNDO,就需要我们动动脑筋找一些新方式来实现了,我们放在难点剖析中叙述。

难点剖析

根据上文,我们已经能够便捷的实现MKDIR–LS的操作,但是TREE和UNDO还没有头绪,这里考察的是对数据结构的设计能力而非纯算法能力了,希望通过这道题的两个难点,能够为你以后做大模拟题带来新的思维。
1、UNDO
前面的操作叙述中我们说过,UNDO不是一种固定操作,而是一种相对一类型1操作(MKDIR\RM\CD)的反操作。这种特点决定了它的具体实现无法在节点结构中封装,而是随着程序的运行,不断变化。这里我们可能会想到:能不能把系统做过的类型1操作存下来呢?
这是一个合理的想法,但是由于记录的对象是操作指令而非节点,我们需要建立一个指令结构,来存放每个指令,再利用vector进行种类1的指令存放就通了!
Command数据结构:

struct Command
{
   
	int command_id;//操作符标识(1-7)
	string command_data;//在前三种有操作目标的操作中记录操作的目标文件
	Dictionary* record_cmd_pos;//在完成了操作后,记录该操作的目的目录地址,如果==NULL,说明该步骤就失败了
}
	

针对几种操作的代码:

void solve()
{
   
	int move_num = 0;
	string s;
	string move_data;
	cin >> move_num;
	Dictionary* now_flag = new Dictionary(NULL, "root");//在开始操作之前,先建立一个根节点,flag是操作的定位节点
	vector<Command*> cmdList;//在每组数据进行操作时,使用vec来记录完成过的所有指令记录
	while (move_num--)
	{
   
		cin >> s;
		Command* command_solve = new Command(s);
		switch (command_solve->command_id)
		{
   
		case 1://MKDIR操作的操作指针扔指向删除这一层
		{
   //在mkdir时,指令内的地址指针指向新建的目录
			command_solve->record_cmd_pos = now_flag->mkdir(command_solve->command_data);
			if (command_solve->record_cmd_pos == NULL) printf("ERR\n");
			else
			{
   
				printf("OK\n");
				cmdList.push_back(command_solve);
			}
			break;
		}
		case 2://RM操作的操作指针扔指向删除这一层
		{
   
			command_solve->record_cmd_pos = now_flag->rm(command_solve->command_data);
			if (command_solve->record_cmd_pos == NULL) printf("ERR\n");
			else
			{
   
				printf("OK\n");
				cmdList.push_back(command_solve);
			}
			break;
		}
		case 3:
		{
   
			Dictionary* tmp_p = now_flag->cd(command_solve->command_data);
			if (tmp_p == NULL) printf("ERR\n");
			else
			{
   
				printf("OK\n");
				command_solve->record_cmd_pos = now_flag;//这条指令指针指向打开的上级目录
				now_flag = tmp_p;//实时的目录指针指向被打开的目录
				cmdList.push_back(command_solve);
			}
			break;
		}
		case 4://SZ
			now_flag->sz();
			break;
		case 5://LS
			now_flag->ls();
			break;
		case 6://TREE
			now_flag->tree();
			break;
		case 7://UNDO
		{
   
			bool undo_or_not = false;
			while (undo_or_not == false && !cmdList.empty())
			{
   
				//从尾部取出一个操作并且进行undo
				command_solve = cmdList.back();
				cmdList.pop_back();
				if (command_solv
  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值