2021-03-18

CCF-CSP 化学方程式

提醒:下面的代码在Dev(C++)模式下编译出错,但是我真的看不出来哪里不对,并且本地用同样的标准编译没问题;而用PAT的OJ编译也是没问题的。所以我用CCF的C++11模式去提交,就100了。很奇怪吧?如果有热心人看出了我哪里有问题,欢迎评论告诉我,谢谢!

思路:是CCF的一贯风格:字符串解析。这题我感觉比JSON查询的难度大,因为这题要找出用栈保存什么还是挺难的。虽然出栈和入栈时机很明显,就是碰到左右括号嘛。最后发现原来需要用栈保存当前列表的化学元素的计数(用一个map表示)。然后就很自然了。

至于题目给的BNF,其实就是告诉你(用一种很正式,很严谨,很。。。的方式)方程式的结构,有好几层,具体来说看看我代码里面ParseN的函数就知道了。前面的几层,即等号,加号的处理,都很简单。难度在于单个化学式的括号可以嵌套,这就用到上面说的栈了,具体写法还是很像JSON的。不要担心内存,用对的数据结构把问题解决了就行。大模拟一般卡不到时间复杂度和空间复杂度的,卡的是设计。

还有就是把后缀数字记法,即H2O的2,用一个函数来实现,这样会方便一些,因为代码有两个地方用到,并且不能把公共部分提出来,只好单独做成函数了。这里和JSON那题字符串抠取也是类似的。如果发现一些代码可以抽出来,并且有好处没坏处,就果断抽出来,这样可以节省时间。

#include <cstdio>
#include <cctype>
#include <cstring>
#include <cassert>
#include <stack>
#include <map>
using namespace std;

typedef map<string,int> Map;

/* run this program using the console pauser or add your own getch, system("pause") or input loop */

const int MAXL=1005;
char str[MAXL];
Map lmp, rmp;
typedef Map::iterator It;

void Print(int l, int r) {
	while (l<=r) {
		putchar(str[l]);
		++l;
	}
	puts("");
}

void PM(Map& mp) {
	for (It it=mp.begin();it!=mp.end();++it) {
		printf("%s %d\n", it->first.c_str(), it->second);
	}
}

void Merge(Map& a, Map& b, int n) {
	// a+=b*n;
	for (It it=b.begin();it!=b.end();++it) {
		a[it->first] += it->second*n;
	} 
}

int FindNum(int& i, int right) {
	if (i<=right && isdigit(str[i])) {
		int n=0;
		while (i<=right && isdigit(str[i])) {
			n=n*10+(str[i]-'0');
			++i;
		}
		return n;
	}
	return 1;
}

Map Parse4(int left, int right) {
	/*
	把一个化学式看做一些项的序列。 
	*/
	stack<Map*> stk;
	// 顶层列表计数。 
	Map* now=new Map;
	int i=left;
	while (i<=right) {
		if (str[i]=='(') {
			// 开始新的列表的计数,保存旧的map。
			stk.push(now);
			now=new Map;
			++i; 
		} else if (str[i]==')') {
			// 当前列表计数完毕。
			++i;
			if (stk.empty()) {
				// 当前的列表就是顶级。
				break; 
			} else {
				int n=FindNum(i, right);
				Map* fa=stk.top();
				stk.pop();
				// 把当前列表合并到上一级列表。 
				Merge(*fa, *now, n);
				delete now;
				now=fa;
			}
		} else {
			// 简单元素,即Na或者H。 
			assert(isupper(str[i]));
			string elem;
			elem.push_back(str[i]);
			++i;
			if (i<=right && islower(str[i])) {
				elem.push_back(str[i]);
				++i;				
			}
			int n=FindNum(i, right);
			(*now)[elem] += n;
		}
	}
//	Print(left, right);
//	PM(*now);
	return *now;
}

void Parse3(Map& mp, int left, int right) {
	// 处理单个化学式。
	int coef=1;
	if (isdigit(str[left])) {
		coef=0;
		while (left <= right && isdigit(str[left])) {
			coef=coef*10+(str[left]-'0');
			++left;
		}
	}
	Map tot=Parse4(left, right);
	Merge(mp, tot, coef);
}

void Parse2(Map& mp, int left, int right) {
	// 分割成+分割的多个公式。
	int i=left;
	while (i <= right) {
		int j=i;
		while (j<=right && str[j]!='+') {
			++j;
		}
		Parse3(mp, i, j-1);
		i=j+1;
	}
}

bool Parse(int left, int right) {
	// 分成=分割的两个公式。
	lmp.clear();
	rmp.clear();
	
	int i=left;
	while (i<=right && str[i] != '=') {
		++i;
	}
	Parse2(lmp, left, i-1);
	Parse2(rmp, i+1, right);
#if 0
	PM(lmp);
	puts("");
	PM(rmp);
#endif

	return lmp == rmp;
}

int main(int argc, char** argv) {
	int N;
	scanf("%d",&N);
	getchar();
	while (N--) {
		fgets(str, sizeof(str), stdin);
		bool ans=Parse(0, strlen(str)-2);
		puts(ans?"Y":"N");
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值