【题解】洛谷P4911 河童重工的计算机

题目链接:P4911 河童重工的计算机

题解

  无可争议的大模拟题写到吐。 题目中附件定义了具体的汇编语言格式,如果不嫌麻烦,一个一个地实现它们,就可以了,具体的实现细节就不逐一介绍了。这里简要介绍一下我自己实现过程中简化代码的方法和遇到的坑点

(1)语句参数的统一处理

  汇编语言中可能涉及到几种不同类型的参数,如常量、编译器常量、寄存器、内存等等。如果每执行一个汇编语句,都要分多种情形处理参数,未免太过繁琐。为了统一对参数的处理,可以定义一个参数类 struct Arg ,定义如下:

struct Arg {
	int type;	// 参数类型, 0: 常量 1: 寄存器 2: 固定位置内存 3: 寄存器位置内存
	int v;		// 获取参数值的辅助信息
	
	// 获取参数的值
	int getV() {
		if (type == 0)	return v;
		else if (type == 1) return reg[v];
		else if (type == 2)	return mem[v];
		return mem[reg[v]];
	} 
	
	// 设置参数的值
	void setV(int a) {
		if (type == 1) reg[v] = a;
		else if (type == 2) mem[v] = a;
		else mem[reg[v]] = a;
	}
}; 

通过两个成员函数 setVgetV 统一处理不同类型参数的赋值操作和取值操作。

(2)查表优化

  题目中需要用到 12 12 12 个寄存器,我们开一个寄存器数组 reg[15] ,数组下标 1 − 12 1-12 112 的位置分别存储这些寄存器的值。读入指令时,从中提取出表示寄存器的字符串,通过查表法,迅速获取寄存器在 reg 中的下标。对应的表的定义如下:

unordered_map<string, int> regType{{"R1", 1}, {"R2", 2}, {"R3", 3}, {"R4", 4}, {"E1", 5}, {"E2", 6}, 
								   {"E3", 7}, {"E4", 8}, {"FLAG", 9}, {"VAL", 10}, {"LINE", 11}, {"RET", 12}};

类似地,给出一条指令的名称,我们也可以通过查表法迅速获取一条指令的操作码(编号 0 − 31 0-31 031),表的定义如下:

unordered_map<string, int> insType{{"UDEF", 0}, {"HLT", 1}, {"NOP", 2}, {"SET", 3}, {"JMP", 4}, {"JIF", 5}, {"CALL", 6},
								   {"RET", 7}, {"INV", 8}, {"ADD", 9}, {"SUB", 10}, {"MULT", 11}, {"IDIV", 12}, {"MOD", 13},
								   {"LSFT", 14}, {"RSFT", 15}, {"BAND", 16}, {"BOR", 17}, {"BXOR", 18}, {"LGR", 19},
								   {"LLS", 20}, {"LGE", 21}, {"LLE", 22}, {"LEQL", 23}, {"LAND", 24}, {"LOR", 25}, {"RINT", 26},
								   {"RCH", 27}, {"WINT", 28}, {"WCH", 29}, {"FUNCTION", 30}, {"CALLFUNC", 31}};

此外,当汇编语句开始模拟执行时,我们可以通过指令的操作码,从对应的函数指针数组中快速找到对应的执行函数,函数指针数组的定义如下:

using pFunc = void (*)(vector<Arg>& );

pFunc exec[32] = {Ins_UDEF, Ins_HLT, Ins_NOP, Ins_SET, Ins_JMP, Ins_JIF, Ins_CALL, Ins_RET, Ins_INV, Ins_ADD,
				  Ins_SUB, Ins_MULT, Ins_IDIV, Ins_MOD, Ins_LSFT, Ins_RSFT, Ins_BAND, Ins_BOR, Ins_BXOR, Ins_LGR,
				  Ins_LLS, Ins_LGE, Ins_LLE, Ins_LEQL, Ins_LAND, Ins_LOR, Ins_RINT, Ins_RCH, Ins_WINT, Ins_WCH,
				  Ins_FUNCTION, Ins_CALLFUNC};

通过上述这些查表优化,我们就省去了许多条件判断,降低了程序复杂度。

(3)个别指令的延迟处理

  在我实现的程序中,对于指令的解析、注释的删除是边输入边处理的,每读入一行,就立即删除注释,把指令解析好,然后再读取下一行。
  然而,个别指令无法在线处理,比如, CALLFUNC 指令,该指令需要调用一个函数,处理该指令时,需要用到函数所在的行的信息。而调用的函数可能在 CALLFUNC 指令的后面才有定义,需要全部读取完输入数据才能确定该函数所在的行数。因此,对该指令作延迟处理
  具体地,每读到一条 CALLFUNC 指令,将其加入一队列中,待 N N N 行程序全部读完,再逐一处理它们。

(4)遇到的坑
  1. 输入数据时,使用 scanf 读取 N N N 后,正式读取汇编程序前,要额外加一句 fgets,吃掉行尾的换行符。这个错误在这篇文章中也有提过。
  2. 注意常量有可能是负数,所以解析参数遇到负号 '-' 要特判一下。
  3. 寄存器的名称中可能含有数字,如 R1E4 等,解析参数时不能遇到非字母字符就中止了。
  4. 打表时,不要把表中的指令的字母拼错。
  5. 注意区分左移 << 和右移 >> ······(谁能想到这是我debug过程中花费时间最长才发现的错误呢?T_T)
(5)代码实现

  附上(不太会有人看的)代码实现如下:

#include <cstdio>
#include <cstdlib>
#include <vector>
#include <string>
#include <unordered_map>

using std::vector;
using std::string;
using std::unordered_map;

unordered_map<string, int> regType{{"R1", 1}, {"R2", 2}, {"R3", 3}, {"R4", 4}, {"E1", 5}, {"E2", 6}, 
								   {"E3", 7}, {"E4", 8}, {"FLAG", 9}, {"VAL", 10}, {"LINE", 11}, {"RET", 12}};

unordered_map<string, int> insType{{"UDEF", 0}, {"HLT", 1}, {"NOP", 2}, {"SET", 3}, {"JMP", 4}, {"JIF", 5}, {"CALL", 6},
								   {"RET", 7}, {"INV", 8}, {"ADD", 9}, {"SUB", 10}, {"MULT", 11}, {"IDIV", 12}, {"MOD", 13},
								   {"LSFT", 14}, {"RSFT", 15}, {"BAND", 16}, {"BOR", 17}, {"BXOR", 18}, {"LGR", 19},
								   {"LLS", 20}, {"LGE", 21}, {"LLE", 22}, {"LEQL", 23}, {"LAND", 24}, {"LOR", 25}, {"RINT", 26},
								   {"RCH", 27}, {"WINT", 28}, {"WCH", 29}, {"FUNCTION", 30}, {"CALLFUNC", 31}};

unordered_map<string, int> funcLoc;

int sp, stk[1 << 22], mem[1 << 24], reg[15];
int N, pc, rp, rcnt, rList[500005], lineStart[100005];
char buf[10005];
bool notefg = 0;	// 标识当前内容是否在注释中 

// 参数 
struct Arg {
	int type;	// 0: constant 1: % 2: @ 3: @%
	int v;
	
	int getV() {
		if (type == 0)	return v;
		else if (type == 1) return reg[v];
		else if (type == 2)	return mem[v];
		return mem[reg[v]];
	} 
	
	void setV(int a) {
		if (type == 1) reg[v] = a;
		else if (type == 2) mem[v] = a;
		else mem[reg[v]] = a;
	}
}; 

void Ins_UDEF(vector<Arg>& arg) { exit(0); }

void Ins_HLT(vector<Arg>& arg) { exit(0); }

void Ins_NOP(vector<Arg>& arg) { ++pc; }

void Ins_SET(vector<Arg>& arg) { ++pc; arg[1].setV(arg[0].getV()); }

void Ins_JMP(vector<Arg>& arg) {
	pc = lineStart[reg[11] + arg[0].getV()];
}

void Ins_JIF(vector<Arg>& arg) {
	++pc;
	if (arg[1].getV()) pc = lineStart[reg[11] + arg[0].getV()];
}

void Ins_CALL(vector<Arg>& arg) {
	++pc;
	stk[++sp] = pc;
	stk[++sp] = reg[11];
	pc = lineStart[arg[0].getV()];
}

void Ins_RET(vector<Arg>& arg) {
	if (arg.size() > 0) reg[12] = arg[0].getV();
	reg[11] = stk[sp--];
	pc = stk[sp--];
}

void Ins_INV(vector<Arg>& arg) { ++pc; arg[1].setV(-arg[0].getV());}

void Ins_ADD(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() + arg[1].getV());}

void Ins_SUB(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() - arg[1].getV());}

void Ins_MULT(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() * arg[1].getV());}

void Ins_IDIV(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() / arg[1].getV());}

void Ins_MOD(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() % arg[1].getV());}

void Ins_LSFT(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() << arg[1].getV());}

void Ins_RSFT(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() >> arg[1].getV());}

void Ins_BAND(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() & arg[1].getV());}

void Ins_BOR(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() | arg[1].getV());}

void Ins_BXOR(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() ^ arg[1].getV());}

void Ins_LGR(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() > arg[1].getV());}

void Ins_LLS(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() < arg[1].getV());}

void Ins_LGE(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() >= arg[1].getV());}

void Ins_LLE(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() <= arg[1].getV());}

void Ins_LEQL(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() == arg[1].getV());}

void Ins_LAND(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() && arg[1].getV());}

void Ins_LOR(vector<Arg>& arg) { ++pc; arg[2].setV(arg[0].getV() || arg[1].getV());}

void Ins_RINT(vector<Arg>& arg) { ++pc; arg[0].setV(rList[rp++]);}

void Ins_RCH(vector<Arg>& arg) { ++pc; arg[0].setV(rList[rp++]);}

void Ins_WINT(vector<Arg>& arg) { ++pc; printf("%d", arg[0].getV());}

void Ins_WCH(vector<Arg>& arg) { ++pc; putchar(arg[0].getV()); }

void Ins_FUNCTION(vector<Arg>& arg) { ++pc; arg[1].setV(arg[0].getV()); }

void Ins_CALLFUNC(vector<Arg>& arg) { 
	++pc; 
	stk[++sp] = pc;
	stk[++sp] = reg[11];
	pc = arg[0].getV();
}

using pFunc = void (*)(vector<Arg>& );

pFunc exec[32] = {Ins_UDEF, Ins_HLT, Ins_NOP, Ins_SET, Ins_JMP, Ins_JIF, Ins_CALL, Ins_RET, Ins_INV, Ins_ADD,
				  Ins_SUB, Ins_MULT, Ins_IDIV, Ins_MOD, Ins_LSFT, Ins_RSFT, Ins_BAND, Ins_BOR, Ins_BXOR, Ins_LGR,
				  Ins_LLS, Ins_LGE, Ins_LLE, Ins_LEQL, Ins_LAND, Ins_LOR, Ins_RINT, Ins_RCH, Ins_WINT, Ins_WCH,
				  Ins_FUNCTION, Ins_CALLFUNC};

struct Instruction {
	int type;
	vector<Arg> arg;
	
	void run() { exec[type](arg); }
	
} ins[55005];

int insp;

void passSpace(char*& p) {
	while (*p && isspace(*p)) ++p;
}

void passNote(char*& p) {
	if (!notefg) return;
	while (*p && *p != ']') ++p;
	if (*p == ']') notefg = 0, ++p;
}

int findEffCon(char*& p) {
	int fg = 0;		// 跳过的非注释内容中, 不包括逗号和分号(0),包括逗号(1),包括分号(2); 
	passNote(p);
	while (*p) {
		if (isspace(*p)) passSpace(p);
		if (*p == '[') {
			notefg = 1;
			++p;
			passNote(p);
		}
		if (*p == ']') ++p;
		if (*p == ',') ++p, fg = 1;
		if (*p == ';') ++p, fg = 2;
		if (isalpha(*p) || isdigit(*p) || *p == '%' || *p == '@' || *p == '#' || *p == '$') break;
	}
	return fg;
}

int getInsType(char*& p) {
	string s = "";
	while (isalpha(*p)) 
		s += (char)toupper(*p), ++p;
	return insType[s];
}

Arg getInsArg(char*& p, int L) {
	int type, v;
	string s = "";
	if (isdigit(*p) || *p == '-') {
		int fg = (*p == '-' ? -1 : 1);
		type = v = 0; 
		while (isdigit(*p))
			v = v * 10 + *p - '0', ++p;
		v *= fg;
	} else if (*p == '#') {
		type = 0;
		v = L;
		++p;
		while (isalpha(*p)) ++p;
	} else if (*p == '%') {
		type = 1;
		++p;
		while (isalpha(*p) || isdigit(*p)) 
			s += (char)toupper(*p), ++p;
		v = regType[s];
	} else if (*p == '@') {
		++p;	
		if (*p == '%') {
			type = 3; ++p;
			while (isalpha(*p) || isdigit(*p)) 
			s += (char)toupper(*p), ++p;
			v = regType[s];
		} else {
			type = 2;
			v = 0;
			while (isdigit(*p))
				v = v * 10 + *p - '0', ++p;
		}
	}
	return (Arg){type, v};
} 

string getFunName(char*& p) {
	string s = "";
	if (*p != '$') return s;
	s += *p;
	++p;
	while (isalpha(*p) || isdigit(*p)) 
		s += *p, ++p;
	return s;
}

void input() {
	notefg = 0;
	scanf("%d", &N);
	fgets(buf, 10002, stdin);
	
	vector<std::pair<int, string>> q;
	for (int i = 1; i <= N; ++i) {
		fgets(buf, 10002, stdin);
		
		char* p = buf;
		findEffCon(p);
		while (*p) {
			int type = getInsType(p);
			++insp;
			ins[insp].type = type;
			if (lineStart[i] == 0) lineStart[i] = insp;
			
			if (type == 7) ins[insp].arg.emplace_back((Arg){1, 12});
			if (type >= 26 && type <= 29) ins[insp].arg.emplace_back((Arg){1, 10});
			
			while (!findEffCon(p)) {
				if (type == 3) {
					ins[insp].arg.emplace_back(getInsArg(p, i));
					findEffCon(p);
					ins[insp].arg.emplace_back(getInsArg(p, i));
				} else if (type == 4 || type == 6) {
					ins[insp].arg.emplace_back(getInsArg(p, i));
				} else if (type == 5) {
					ins[insp].arg.emplace_back(getInsArg(p, i));
					if (findEffCon(p) == 2) {
						ins[insp].arg.emplace_back((Arg){1, 9});
						break;
					}
					ins[insp].arg.emplace_back(getInsArg(p, i));
				} else if (type == 7) {
					ins[insp].arg.pop_back();
					ins[insp].arg.emplace_back(getInsArg(p, i));
				} else if (type == 8) {
					ins[insp].arg.emplace_back(getInsArg(p, i));
					if (findEffCon(p) == 2) {
						ins[insp].arg.emplace_back((Arg){1, 10});
						break;
					}
					ins[insp].arg.emplace_back(getInsArg(p, i));
				} else if (type >= 9 && type <= 18) {
					ins[insp].arg.emplace_back(getInsArg(p, i));
					findEffCon(p);
					ins[insp].arg.emplace_back(getInsArg(p, i));
					if (findEffCon(p) == 2) {
						ins[insp].arg.emplace_back((Arg){1, 10});
						break;
					}
					ins[insp].arg.emplace_back(getInsArg(p, i));
				} else if (type >= 19 && type <= 25) {
					ins[insp].arg.emplace_back(getInsArg(p, i));
					findEffCon(p);
					ins[insp].arg.emplace_back(getInsArg(p, i));
					if (findEffCon(p) == 2) {
						ins[insp].arg.emplace_back((Arg){1, 9});
						break;
					}
					ins[insp].arg.emplace_back(getInsArg(p, i));
				} else if (type >= 26 && type <= 29) {
					ins[insp].arg.pop_back();
					ins[insp].arg.emplace_back(getInsArg(p, i));
				} else if (type == 30) {
					funcLoc[getFunName(p)] = insp;
					ins[insp].arg.emplace_back((Arg){0, i});
					ins[insp].arg.emplace_back((Arg){1, 11});
				} else if (type == 31) {
					q.emplace_back(make_pair(insp, getFunName(p)));
				}
			}
		}
	}
	for (auto& [pos, s] : q) {
		ins[pos].arg.emplace_back((Arg){0, funcLoc[s]});
	}
	
	while (scanf("%d", rList + rcnt) == 1) ++rcnt;
}

int main() {
	pc = 1; 
	rp = rcnt = insp = 0;
	
	input();
	
	while (1) ins[pc].run();
	
	return 0;
}

总结

  写完这题,我终于可以说自己做过大模拟了。调试遇到了不少 Bug \text{Bug} Bug,为了定位一个错误的具体位置,我用肉眼扫过那一个个 50000 + 50000+ 50000+ 行的输入文件和巨长的输出······最后,竟然是左移和右移搞反了,直接裂开。
  虽然说这题的 AC \text{AC} AC 代码足足达到了 300 + 300+ 300+ 行,刷新了我写 OJ \text{OJ} OJ 的代码行数的记录,但是和标程相比,和部分别人的代码相比,还算是短的(欣慰ing)。
  之前写 OJ \text{OJ} OJ 题,多半几十行左右,名义上是 C++ \text{C++} C++,实则属于面向过程的编程,和 C \text{C} C 差不了多少(除去部分STL容器)。代码量达到一定规模,面向对象的思想就尤为重要了,这便是现今工程代码所关注的,此时, C/C++ \text{C/C++} C/C++ 中部分平时不常用的特性在这个时候就有用武之地了。所以,还是要多写写这类题吧······去尝试,去实践,去发现,去创造新的可能。




  (画外音
  “干得不错,后面还有20+道类似的(更难的)大模拟题等着你呐。”
  “不!!!!!”(蟹阿金的既视感)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值