[省选联考 2022] 预处理器 题解

来自bzoj(传统题)

目录

题面:

思路:

输入:

宏的实现:

定义宏:

删除宏:

展开的实现:

直接展开:

连续展开:

递归展开:

综合:

代码:



题面:


宏是 C/C++ 语言的一项特性,它根据预先定义的规则进行文本替换(也被称为 “宏展开”),能够实现定义常量、简化代码重复输入等功能。例如:

#define PI 3.14159
double area = PI * r * r;
以上代码经过宏展开后变为:

double area = 3.14159 * r * r;

其中,宏定义命令变成了空行,而其他行中的宏被展开成了规则定义的文本。

C/C++ 语言代码在编译时对宏的处理由预处理器完成。你的任务是实现一个简化版的预处理器,要求如下:

  • 代码由组成,每行除行末的换行符外,均由可打印 ASCII 字符(ASCII 码范围 32∼126)组成。每行要么是预处理命令(以 # 开头),要么是普通文本(其他情况)。

  • 预处理器逐行处理代码,

    • 如果是预处理命令,执行该命令,并输出一个空行。
    • 如果是普通文本,对其进行宏展开并输出结果。
  • 预处理命令有两种,分别是宏定义命令 #define 和取消宏定义命令 #undef

    • 宏定义命令的格式为 #define <name> <content>,其中第一部分 #define 是命令名,第二部分 <name> 是要定义的宏的名字,第三部分 <content> 是要定义的宏的展开内容。
    • 取消宏定义命令的格式为 #undef <name>,其中第一部分 #undef 是命令名,第二部分 <name> 是要取消的宏的名字。

    以上两种预处理命令中,相邻两部分之间都严格用一个空格分隔。<name> 是由大小写字母和数字以及下划线组成的标识符(一个或多个字符),<content> 可以包含任意可打印 ASCII 字符(零个或多个字符)。一个宏定义的有效范围是从它定义所在行开始到后续最近的宏名匹配的取消定义所在行为止(如果没有对应的取消定义,则有效范围一直覆盖到文件结束)。

对普通文本进行宏展开时,将一行文本中每段连续极长的大小写字母和数字以及下划线视为标识符(而不是其中一部分),其余为其他字符。从左到右依次对文本中的标识符进行宏展开:

  1. 如果该标识符是有效的宏名,则用对应的展开内容替换它,此时该宏名进入正在展开的状态,直到本流程结束;否则原样保留宏名。例如,若宏 A 定义为 b,则文本 A 展开结果为 b(发生替换),文本 B 展开结果仍然为 B(未定义,不替换),文本 AA 展开结果仍然为 AAAA 是不同于 A 的另一个标识符,未定义),而文本 A*B 展开结果为 b*B
  2. 替换发生后,如果展开内容中包含标识符,重复应用以上的展开操作,称为 “多次展开”。例如,若宏 A 定义为 B,宏 B 定义为 c,则文本 A 的展开结果为 c
  3. 如果待展开的宏名与正在进行展开的某个宏名相同,称为 “递归展开”,此时该宏名不再展开。本规则用来防止无限递归展开。例如,若宏 A 定义为 B+a,宏 B 定义为 A+b,则文本 A 展开结果为 A+b+a,由于最初的 A 处于正在展开状态,因此 A+b+a 里的 A 不再展开。
  4. 其他字符原样保留。

注意:出于简化的目的,本题的要求与 C/C++ 语言标准里的描述不完全一致,请以上面的要求为准。最明显的区别是本题只有标识符和其他字符两类词法单元,没有数值、字符串、注释等。


非常复杂的模拟题,毕竟是省赛题啊(作者已肝硬化)。

思路:


输入:

看过上期程序分析机应该就能知道用getline(instruction),但是,这次额外输入了一个行数,所以输入行数后,要再进行一次getline消除回车。由于输入的代码和标准C/C++不一致,所以define不会向后查找宏,所以只需一个变量即可。

宏的实现:

定义宏:

定义一个以字符串做索引,存放字符串的map(macro),每次getline之后,如果instruction的第1(索引为0)个字符为#且第2个字符为d,说明是个define语句,跳过#define和其后的空格(将下标设为8),按照空格分成name和content两个字符串,把macro[name]设为content。

void define()
{
	string name = "",content = "";
	int location = 8;
	while(instruction[location] != ' ') name += instruction[location],location++;
	location++;
	while(location < len) content += instruction[location],location++;
	macro[name] = content;
	return;
}

删除宏:

如果instruction的第1个字符为#但第2个字符为u,说明是个undef语句,跳过#undef和其后的空格(将下表设为7),剩余的字符存为name,把macro[name]设为空。

void undef()
{
	string name = "";
	int location = 7;
	while(location < len) name += instruction[location],location++;
	macro[name] = "";
}

展开的实现:

直接展开:

从左往右按照非标准字符(非字母、非数字(无此要求))分块,若macro[某一区块]不为空,则把这一区块替换为macro。

bool judge(char character)
{
	if((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z')) return true;
	return false;
}
string launch(string instruction)
{
    string ans,str;
    int r = 0;
    while(r < instruction.size())
    {
        while(r < instruction.size() && judge(instruction[r])) ans += instruction[r],r++;
        if(macro[ans] != "") str += macro[ans];
        else str += ans;
        while(r < instruction.size() && !judge(instruction[r])) r++;
    }
    return str;
}

连续展开:

通过递归,在找出ans且ans是个宏之后,以ans为参数再进行launch判断。

string launch(string instruction)
{
    string ans,str;
    int right = 0;
    while(right < instruction.size())
    {
        while(right < instruction.size() && judge(instruction[right])) ans += instruction[right],right++;
        if(macro[ans] != "") str += launch(macro[ans]);
        else str += ans;
        while(right < instruction.size() && !judge(instruction[right])) right++;
    }
    return str;
}

递归展开:

上面的代码,如果出现递归展开,会死循环,最后内存RE。

我们再开一个以字符串为索引,存放布尔值的map(change),在进行递归操作之前,把change[ans]设为true,并且判断ans是否为宏时,判断change[ans]必须不为true,执行完递归后还原change[ans]。

string launch(string instruction)
{
    string ans,str;
    int right = 0;
    while(right < instruction.size())
    {
        while(right < instruction.size() && judge(instruction[right])) ans += instruction[right],right++;
        if(macro[ans] != "" && !change[ans])
        {
            change[ans] = 1;
            str += launch(macro[ans]);
            change[ans] = 0;
        }
        else str += ans;
        while(right < instruction.size() && !judge(instruction[right])) right++;
    }
    return str;
}

综合:

在进行合并和优化、美化之后,代码长这样:

string launch(string instruction_copymain)
{
	string instruction_launch = "",instruction_copy2 = "";
	int len_copymain = instruction_copymain.size();
	int left = 0,right = 0;
	while(left < len_copymain)
	{
		while(right < len_copymain && judge(instruction_copymain[right])) right++;
		instruction_copy2 = "";
		for(int i = left;i < right;i++) instruction_copy2 += instruction_copymain[i];
		if(macro[instruction_copy2].size() < 1 || change[instruction_copy2]) instruction_launch += instruction_copy2;
		else
		{
			change[instruction_copy2] = 1;
			instruction_launch += launch(macro[instruction_copy2]);
			change[instruction_copy2] = 0;
		}
		while(right < len_copymain && !judge(instruction_copymain[right])) instruction_launch += instruction_copymain[right],right++;
		left = right;
	}
	return instruction_launch;
}

代码:

#include<iostream>
#include<cstring>
#include<map>
using namespace std;
int lines,len;
string instruction;
map <string,string> macro;
map <string,bool> change;
bool judge(char character)
{
	if((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z')) return true;
	return false;
}
void define()
{
	string name = "",content = "";
	int location = 8;
	while(instruction[location] != ' ') name += instruction[location],location++;
	location++;
	while(location < len) content += instruction[location],location++;
	macro[name] = content;
	return;
}
void undef()
{
	string name = "";
	int location = 7;
	while(location < len) name += instruction[location],location++;
	macro[name] = "";
}
string launch(string instruction_copymain)
{
	string instruction_launch = "",instruction_copy2 = "";
	int len_copymain = instruction_copymain.size();
	int left = 0,right = 0;
	while(left < len_copymain)
	{
		while(right < len_copymain && judge(instruction_copymain[right])) right++;
		instruction_copy2 = "";
		for(int i = left;i < right;i++) instruction_copy2 += instruction_copymain[i];
		if(macro[instruction_copy2].size() < 1 || change[instruction_copy2]) instruction_launch += instruction_copy2;
		else
		{
			change[instruction_copy2] = 1;
			instruction_launch += launch(macro[instruction_copy2]);
			change[instruction_copy2] = 0;
		}
		while(right < len_copymain && !judge(instruction_copymain[right])) instruction_launch += instruction_copymain[right],right++;
		left = right;
	}
	return instruction_launch;
}
signed main()
{
	cin >> lines;
	getline(cin,instruction);
	for(int i = 1;i <= lines;i++)
	{
		getline(cin,instruction);
		len = instruction.size();
		if(instruction[0] == '#')
		{
			if(instruction[1] == 'd') define();
			else undef();
		}
		else cout << launch(instruction) << endl;
	}
}

  • 12
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
USACO2022金组是国际在线判题系统USACO的最高级别,题目难度较高,在该比赛中取得好成绩是一项巨大的成就。以下是对该比赛的一些题目解析。 第一题:“交通计划” 题目要求:给定一个n个节点的有向图,每条边有一个长度,希望添加最少的边使得所有节点连通,求最小生成树的权值和。 解析:该题可以使用Kruskal算法求解,将每条边按权值从小到大排序,再依次加入,判断加入的边是否会形成环,若形成则不加入,直到所有节点连通为止。此时Kruskal算法得到的最小生成树的权值和即为所求。 第二题:“点火计划” 题目要求:给定一个n个节点的有向图,每条边有一个权值和一个点火时长,每个节点有一个点火启动时刻和时刻结束时刻,希望从其中选出一些边点火,使得所有节点都可从点火的边出发到达,且所选点火边的总点火时长最小。 解析:该题可以使用最小费用最大流算法求解。将每条边看做一个容量为1,费用为点火时长的边,源点向节点的点火边容量为1,费用为0的边,节点的点火边向汇点的容量为1,费用为0的边,对这个网络进行最小费用最大流即可得到所选边的总点火时长最小。 第三题:“美味佳肴” 题目要求:给定n个菜品,每个菜品有它的权值和两个类别,希望选出k个菜品,使得选出的菜品数量在每个类别中都不超过$\frac{k}{3}$个,且所选菜品的权值和最大。 解析:该题可以使用动态规划求解。设$f[i][j][k]$表示前i个菜品中,选择j个一类菜品,选择k个二类菜品的最大权值和,状态转移方程为$f[i][j][k]=max(f[i-1][j][k],f[i-1][j-1][k]+a[i],f[i-1][j][k-1]+b[i])$,其中a[i]为i号菜品的权值,若为一类则为该权值,否则为0,b[i]为i号菜品的权值,若为二类则为该权值,否则为0。最终答案为$f[n][$k/3$][$k/3$]。 以上是对USACO2022金组的部分题目的解析,USACO比赛是全球范围内的计算机竞赛,竞争非常激烈,能够在该比赛中脱颖而出是一项非常棒的成就。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值