题解 SP147 【TAUT - Tautology】

Update 2021/04/12

更新了题目中的错别字以及 \(\text{markdown}\) 代码错误使用情况。

想法简述

这道题主要是考察表达式求值的能力。

所谓重言式就是无论变量值结果都为 \(\text{true}\),所以只要把所有情况枚举一遍就好了。

题目分析

\(C\) -- \(\operatorname{and}\)

\(D\) -- \(\operatorname{or}\)

\(I\) -- \(\operatorname{implies}\)

\(E\) -- \(\operatorname{if}\ \operatorname{and}\ \operatorname{only}\ \operatorname{if}\)

\(N\) -- \(\operatorname{not}\)


\(\operatorname{and}\)\(\operatorname{or}\) 不作解释。

\(a\ \operatorname{implies}\ b\) 代表如果 \(a\) 成立,那么 \(b\) 成立,如果 \(a\) 不成立,那么 \(b\) 可能成立,换句话说就是相当于 \(\operatorname{not}\ a\ \operatorname{or}\ b\), \(\operatorname{not}\) 优先级高于 \(\operatorname{or}\)

\(a\ \operatorname{if}\ \operatorname{and}\ \operatorname{only}\ \operatorname{if}\ b\) 代表如果 \(a\) 成立那么 \(b\) 成立,如果 \(a\) 不成立那么 \(b\) 也不成立,所以相当于 \([a=b]\)

\(\operatorname{not}\ a\),大家清楚,但是前缀表达式 \(\text{NCNpp}\),其实是应该是 \(\operatorname{not}(\operatorname{not}\ p \ \operatorname{or} \ p)\) 而不是 \(\operatorname{not}(p\ \operatorname{or} \ \operatorname{not} p)\)

然后再刷一趟表达式求值就好了。

表达式求值

这次的表达式和以往的不太一样所以使用递归来解。

先来观察以下几种情况:

\(\text{Eab}\)

\(\text{EEabb}\)

\(\text{EEEabEabEab}\)

\(\text{EEEEabbbb}\)

第一个就肯定是 \(\text{aEb}\)

第二个先处理 \(\text{Eab}\), 再处理 \(\text{EEabb}\),那么就是 \(\text{(aEb)Eb}\)

第三个就是 \(\text{(aEb)E((aEb)E(aEb))}\) 了。

第四个就是 \(\text{aEbEbEbEb}\)

不难发现,当一段的运算符个数等于变量个数减一,那么可以分成两个子问题,那么用分治的想法就可以解完这道题。

代码

int T,N,nn;
char s[120],S[120];
int vis[30],cnt;
bool checker;
bool Nt[120];

bool check(int now,int L,int R){
	bool A,B; int cnt=0, tot=0;
	if(L==R) return ((now>>(vis[s[L]-'a'+1]-1))&1)^Nt[L];
	for(int i=L;i<=R;i++){
	//tot代表数字的数目,cnt代表符号的数目,当数字和符号数目一样时就是下一次的状态,这里不做解释了
		if('a'<=s[i]&&s[i]<='z') tot++;
		else cnt++;
		if(tot==cnt){
			A=check(now,L+1,i);
			B=check(now,i+1,R);
			break;
		}
	}
	switch(s[L]){
		case'C':return( A&&B)^Nt[L]; //这里就是4种关系式判断了,Nt数组存的是当前这次运算是否需要加not
		case'D':return( A||B)^Nt[L];
		case'I':return(!A||B)^Nt[L];
		case'E':return( A==B)^Nt[L];
	}
}

void get_string(){
	memset(Nt,0,sizeof Nt); memset(vis,0,sizeof vis); N=nn=0;
	for(char ch=getchar();ch!='\n'&&ch!=EOF;ch=getchar()) s[++N]=ch;
	for(int i=1;i<=N;i++) if(s[i]!='N') S[++nn]=s[i]; else Nt[nn+1]=!Nt[nn+1];
	//把not单独判断可以省好多事
	memset(s,0,sizeof s); for(int i=1;i<=nn;i++) s[i]=S[i]; N=nn; return;
}

int main(){
//	freopen("SP147.in","r",stdin);
//	freopen("SP147.out","w",stdout);
	cin>>T; getchar();
	while(T--){
		get_string();
		checker = true; cnt=0;
		for(int i=1;i<=N;i++)
		if('a'<=s[i]&&s[i]<='z')
		if(!vis[s[i]-'a'+1])
			vis[s[i]-'a'+1]=++cnt;
		for(int i=(1<<cnt)-1;i>=0;i--) //枚举现在的状态
		if(check(i,1,N)==false){checker=false; break;}
		printf(checker?"YES\n":"NO\n");
	}
	return 0;

还有我的造数据的代码,这里就不解释了!

#include<bits/stdc++.h>
#include<windows.h>
using namespace std;

void DFS(int L,int R){
	if(L==R){
		if(rand()&1)putchar('N');
		putchar(rand()%26+'a');
		return;
	}
	putchar(rand()&1?rand()&1?'C':'D':rand()&1?'I':'E');
	int mid=(rand()%(R-L))+L; if(mid==R) mid--;
//	printf("%d %d %d\n",L,mid,R);
	DFS(L,mid); DFS(mid+1,R);
	return;
}

int main(){
	freopen("SP147.in","w",stdout);
	srand(GetTickCount());
	int N=rand()%9+2;
	putchar('1'); putchar(10);
	DFS(1,N);
	return 0;
} 

Made By \(\operatorname{wheneveright}\)

posted @ 2021-04-12 19:03  XJ21  阅读(126)  评论(0)    收藏  举报