[题解] [CSP-J 2022] 逻辑表达式 思路整理

标签:分治

题目传送门:P8815 [CSP-J 2022] 逻辑表达式

题目大意

给一个包含 01|&() 的逻辑表达式(保证正确)。

在计算表达式时采用“短路”策略:

  • 计算表达式 a&b 时,先计算 a,如果表达式 a 的值为 \(0\),那么表达式的值一定为 \(0\),无需计算表达式 b,记为一处 & 短路
  • 计算表达式 a|b 时,先计算 a,如果表达式 a 的值为 \(1\),那么表达式的值一定为 \(1\),无需计算表达式 b,记为一处 | 短路

现需计算表达式的值,并输出计算过程中 & 短路| 短路 的出现次数。

注:在一个逻辑表达式中还可能有括号。规定在运算时,括号内的部分先运算;两种运算并列时,& 运算优先于 | 运算;同种运算并列时,从左向右运算。

思路

首先观察样例。 考虑计算过程。

z9q9rF.png

首先你观察到了中间的 |,并从这里把表达式分成了两段。

z9qEP1.png

你先考虑左边那一段。你观察到了中间的 &,并从这里把左半部分表达式分成了两段。

你发现左半部分表达式的左半部分值为 \(0\),你进行了一次 & 短路

z9qV8x.png

于是表达式变成了这样:

z9qZ26.png

现在你把左半部分处理完了,你将目光投向右半部分。

你观察到了一个 |,并从这里把右半部分表达式分成了两段。

z9qoJ1.png

你开始处理左半部分的右半部分。你观察到了一个 |,并从这里把表达式分成了两段。

z9qTRx.png

你发现左半部分表达式的左半部分值为 \(1\),你进行了一次 | 短路

于是表达式变成了这样:

z9q7z6.png

然后你注意到先前观察到的 | 左半部分值为 \(1\),你进行了一次 | 短路

于是表达式变成了这样:

z9qqsO.png

智慧的你一眼看出表达式的值为 \(1\)

综上,表达式的值为 \(1\),计算过程中,你进行了 \(1\)| 短路\(2\)& 短路


这是人工处理表达式时的计算过程,那么又该如何让程序实现这个过程呢?

注意到每次处理时,首先找到了当前层(一对括号记为一层)最靠右的 |,并从该位置分成左右两段分别进行操作。因此是对原表达式进行分治

当没有 | 时,去寻找最靠右的 &,进行类似的分段处理。

但如果直接进行分治,每次遍历一遍字符串去找最右边的 | 或者 &,会 TLE 到飞起。

考虑进行一个预处理。对于每个位置,处理出与它同层的字符中,离它最近的 | 或者 & 在哪里。

不难写出以下程序:

int cnt = 0;
	for (int i = 1; i <= n; i++) {
		if (c[i] == '(') cnt++;
		if (c[i] == ')') lastor[cnt] = lastand[cnt] = 0, cnt--;
		if (c[i] == '|') lastor[cnt] = i;
		if (c[i] == '&') lastand[cnt] = i;
		sand[i] = lastand[cnt], sor[i] = lastor[cnt];
	}

其中 \(lastor_{cnt}\) 表示第 \(cnt\) 层最后一个 | ​的位置,\(lastand_{cnt}\) 表示第 \(cnt\) 层最后一个 & 的位置。

\(sor_i\) 表示与第 \(i\) 位同层的字符中,最右边的 | 的位置,\(sand_i\) 表示与第 \(i\) 位同层的字符中,最右边的 & 的位置。

注意:这个“同层”指的是在同一对括号内,所以要记得清空 \(lastor\)\(lastand\)不过好像不清空也是对的(因为具体实现时会判掉不在同一括号内的情况)

那么也不难写出分治的函数了:

int solve(int l, int r) {
	if (sor[r] >= l) { // 区间内有 '|' 
		int res = solve(l, sor[r] - 1);
		if (res == 1) {
			cntor++;
			return 1;
		}
		return (res | solve(sor[r] + 1, r));
	} 
	if (sand[r] >= l) { // 区间内有 '&' 
		int res = solve(l, sand[r] - 1);
		if (res == 0) {
			cntand++;
			return 0;
		}
		return (res & solve(sand[r] + 1, r));
	}
	if (c[l] == '(' && c[r] == ')') return solve(l + 1, r - 1); // 匹配到一对括号, 将其去掉 
	return c[l] ^ 48; // 以上情况都不是, 那么只能是数字 
}

注意:拆括号不能放在函数的最开头。

为什么?因为我错过,请观察一下样例二,如果在开头去括号,当你由最右边的 & 把表达式分成两段后,你会将第 \(1\) 位的括号和倒数第 \(3\) 位的括号匹配起来并去掉。但事实上它们并不是一对,于是样例二就寄了。

完整代码

const int N = 1000010;
char c[N];
int n, sand[N], sor[N], lastand[N], lastor[N];
int cntand, cntor;
int solve(int l, int r) {
	if (sor[r] >= l) {
		int res = solve(l, sor[r] - 1);
		if (res == 1) {
			cntor++;
			return 1;
		}
		return (res | solve(sor[r] + 1, r));
	} 
	if (sand[r] >= l) { 
		int res = solve(l, sand[r] - 1);
		if (res == 0) {
			cntand++;
			return 0;
		}
		return (res & solve(sand[r] + 1, r));
	}
	if (c[l] == '(' && c[r] == ')') return solve(l + 1, r - 1);
	return c[l] ^ 48;
}
int main() {
	scanf("%s", c + 1);
	n = strlen(c + 1);
	int cnt = 0;
	for (int i = 1; i <= n; i++) {
		if (c[i] == '(') cnt++;
		if (c[i] == ')') lastor[cnt] = lastand[cnt] = 0, cnt--;
		if (c[i] == '|') lastor[cnt] = i;
		if (c[i] == '&') lastand[cnt] = i;
		sand[i] = lastand[cnt], sor[i] = lastor[cnt];
	}
	int ans = solve(1, n);
	printf("%d\n%d %d\n", ans, cntand, cntor);
	return 0;
}

完结撒花~

posted @ 2022-11-11 09:31  shiranui  阅读(1451)  评论(0编辑  收藏  举报
*/