2023“钉耙编程”中国大学生算法设计超级联赛(9)- 1003 Reasoning 题解
题目翻译
基本符号
有一推理系统,其中有这些符号:
- 括号 \((\) 和 \()\);
- 逻辑连接词 \(\lnot\) 和 \(\rightarrow\);
- 全称量词 \(\forall\);
- 变量 \(u\sim z\);
- 常量 \(a\sim e\);
- 函数 \(f\sim h\);
- 谓词 \(P\sim T\)。
这些符号是构成系统的基础,他们之间能够组合出一些其他概念:
项(term)
- 变量和常量都叫做项;
- 假设 \(t_1,\ldots,t_n\) 都是项且 \(f\) 是函数,则 \(f\,t_1\ldots t_n\) 也是项。
也就是说,变量、常量和函数的组合就是项。
公式(formula)
- 假设 \(t_1,\ldots,t_n\) 都是项且 \(P\) 是谓词,则 \(P\, t_1\ldots t_n\) 是公式。这种公式被称为原子公式;
- 假设 \(\varphi\) 是公式,那么 \((\lnot \varphi)\) 也是公式;
- 假设 \(\varphi\) 和 \(\psi\) 都是公式,那么 \((\varphi\rightarrow \psi)\) 也是公式;
- 假设 \(\varphi\) 是公式,\(v\) 是变量,那么 \(\forall v \varphi\) 也是公式。
公式有四种定义,其中第二三种定义只是公式间的组合。下面的定义全部都基于这四种公式的定义。
自由出现(free occurrence)
对于公式 \(\varphi\) 和变量 \(x\),\(x\) 在 \(\varphi\) 中自由出现的定义为:
- 假设 \(\varphi\) 是原子公式,当且仅当 \(x\) 在 \(\varphi\) 中出现(字符 \(x\) 在字符串 \(\varphi\) 中)就称 \(x\) 在 \(\varphi\) 中自由出现;
- 假设 \(\varphi\) 形如 \((\lnot\psi)\),则 \(x\) 在 \(\varphi\) 中自由出现当且仅当 \(x\) 在 \(\psi\) 中自由出现;
- 假设 \(\varphi\) 形如 \((\psi\rightarrow \gamma)\),则 \(x\) 在 \(\varphi\) 中自由出现当且仅当 \(x\) 在 \(\psi\) 或 \(\gamma\) 中自由出现;
- 假设 \(\varphi\) 形如 \(\forall v \psi\),则 \(x\) 在 \(\varphi\) 中自由出现当且仅当 \(x\neq v\) 且 \(x\) 在 \(\psi\) 中自由出现。
可知,\(x\) 在 \(\forall v \varphi\) 中自由出现的必要条件是 \(x\neq v\),即 \(x\) 在 \(\forall x \varphi\) 中不可能自由出现。我们可以把 \(\forall x\) 看作是 \(x\) 被“锁住”了。
替换(replacement)
对于公式 \(\varphi\)、变量 \(x\) 和项 \(t\),替换 \(\varphi_t^x\) 定义为:
- 假设 \(\varphi\) 是原子公式,则 \(\varphi_t^x\) 表示将 \(\varphi\) 中的每一个 \(x\) 都替换成 \(t\);
- 假设 \(\varphi\) 形如 \((\lnot \psi)\),则 \(\varphi_t^x=(\lnot\psi)_t^x=(\lnot\psi_t^x)\);
- 假设 \(\varphi\) 形如 \((\psi\rightarrow\gamma)\),则 \(\varphi_t^x=(\psi\rightarrow\gamma)_t^x=(\psi_t^x\rightarrow\gamma_t^x)\);
- 假设 \(\varphi\) 形如 \(\forall v\psi\),则 \(\varphi_t^x = (\forall v\psi)_t^x = \begin{cases}\forall v\psi&x=v\\\forall v(\psi_t^x)&x\neq v\end{cases}\)。
也就是说,假设 \(x\) 在公式 \(\forall x\varphi\) 中被锁住了,那么该公式的替换就是其本身。
零冲突替换(zero-contradiction replacement)
对于公式 \(\varphi\)、变量 \(x\) 和项 \(t\),零冲突替换定义为:
- 假设 \(\varphi\) 为原子公式,\(t\) 恒能在 \(\varphi\) 中零冲突替换 \(x\);
- 假设 \(\varphi\) 形如 \((\lnot \psi)\),则 \(t\) 能在 \(\varphi\) 中零冲突替换 \(x\) 当且仅当 \(t\) 能在 \(\psi\) 中零冲突替换 \(x\);
- 假设 \(\varphi\) 形如 \((\psi\rightarrow \gamma)\),则 \(t\) 能在 \(\varphi\) 中零冲突替换 \(x\) 当且仅当 \(t\) 能在 \(\psi\) 和 \(\gamma\) 中同时零冲突替换 \(x\);
- 假设 \(\varphi\) 形如 \(\forall v\psi\),则 \(t\) 能在 \(\varphi\) 中零冲突替换 \(x\) 当且仅当满足下面条件中的任意一个:
- \(x\) 在 \(\varphi\) 中没有自由出现;
- \(v\) 没有在 \(t\) 中出现,且 \(t\) 能在 \(\psi\) 中零冲突替换 \(x\)。
即,\(t\) 能在 \(\forall v\varphi\) 中零冲突替换 \(x\),当且仅当 \(x\) 在 \(\forall v\varphi\) 中不存在或被锁,或是被锁住的 \(v\) 没在 \(t\) 中出现且 \(t\) 能继续往下递归。
样例
现在给出公式 \(A\)、变量 \(x\) 和项 \(t\),再给出每个函数或谓词的参数个数,\(Q\) 次询问 \(t\) 是否能在 \(A\) 中零冲突替换 \(x\),若能还要给出替换后的 \(A_t^x\)。
给出的公式中,\(\lnot\) 用 \(N\) 代替,\(\rightarrow\) 用 \(I\) 代替,\(\forall\) 用 \(A\) 代替。
样例输入:
1
AxAy(PzvfxygxyI(NQ))
5
f 3
g 2
P 3
Q 0
h 2
4
hxx x
hxy y
hzz z
hxz z
样例输出:
Y
AxAy(PzvfxygxyI(NQ))
Y
AxAy(PzvfxygxyI(NQ))
Y
AxAy(PhzzvfxygxyI(NQ))
N
样例的公式 \(A = \forall x\forall y(P(z,v,f(x,y,g(x,y)))\rightarrow(\lnot Q))\)。
题解
把整棵树建出来然后暴力跑即可。。。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#define maxn 2005
using namespace std;
int T,n,m,x,Q; char a[maxn],ch,trans[maxn],repl; map<char,int> para;
struct node{char opt,var; int to[maxn],num,fa;}t[maxn]; int now=1,cnt=1;
// opt 存符号,若为全称量词则记录锁住的变量 var;to 存子节点,num 计个数;fa 存父节点
void build(int pos,int las){ // now 记录到公式 A 的哪一位,pos 记录树节点编号,las 记录父节点编号
if(now>=n) return; if(a[now]=='A'){
t[pos].fa=las; t[pos].opt='A'; t[pos].var=a[now+1]; t[las].to[++t[las].num]=pos;
now+=2; build(++cnt,pos); // 把全称量词和锁住的变量统计
}else if(a[now]=='('){
t[pos].fa=las; t[pos].opt='('; t[las].to[++t[las].num]=pos; now++; build(++cnt,pos);
if(a[now]==')'){now++; return;} if(a[now]=='I'){now++; build(++cnt,pos);} if(a[now]==')'){now++; return;}
// 一定要注意判右括号
}else if((a[now]>='f'&&a[now]<='h')||(a[now]>='P'&&a[now]<='T')){
t[pos].fa=las; t[pos].opt=a[now]; t[las].to[++t[las].num]=pos; char res=a[now];
now++; for(int i=1;i<=para[res];i++) build(++cnt,pos);
// 遇到函数把所有参数统计完就返回
}else if(a[now]=='N'){
t[pos].fa=las; t[pos].opt='N'; t[las].to[++t[las].num]=pos; now++; build(++cnt,pos);
}else{t[pos].fa=las; t[pos].opt=a[now]; t[las].to[++t[las].num]=pos; now++;}
}
bool free_ocr(int pos,char keyw){ // 判断是否自由出现
if(t[pos].opt>='u'&&t[pos].opt<='z') return (t[pos].opt==keyw); if(t[pos].opt>='a'&&t[pos].opt<='e') return 0;
if((t[pos].opt>='f'&&t[pos].opt<='h')||(t[pos].opt>='P'&&t[pos].opt<='T')||t[pos].opt=='N'||t[pos].opt=='(')
{bool flag=0; for(int i=1;i<=t[pos].num;i++) flag|=free_ocr(t[pos].to[i],keyw); return flag;}
if(t[pos].opt=='A'&&t[pos].var==keyw) return 0; return free_ocr(t[pos].to[1],keyw);
}
bool check(int pos){ // 判断能否零冲突替换
if(t[pos].opt>='P'&&t[pos].opt<='T') return 1; if(t[pos].opt=='N') return check(t[pos].to[1]);
if(t[pos].opt=='('){bool flag=1; for(int i=1;i<=t[pos].num;i++) flag&=check(t[pos].to[i]); return flag;}
if(t[pos].opt=='A'){
if(!free_ocr(pos,repl)) return 1; int llen=strlen(trans);
for(int i=0;i<llen;i++) if(trans[i]==t[pos].var) return 0; return check(t[pos].to[1]);
} return 1;
}
void output(int pos,bool on){ // 替换并输出,on 表示要不要替换(可能前面被锁了)
if(t[pos].opt>='a'&&t[pos].opt<='e') printf("%c",t[pos].opt);
if(t[pos].opt>='u'&&t[pos].opt<='z'){if(t[pos].opt==repl&&on) printf("%s",trans); else printf("%c",t[pos].opt);}
if((t[pos].opt>='f'&&t[pos].opt<='h')||(t[pos].opt>='P'&&t[pos].opt<='T')||t[pos].opt=='N'||t[pos].opt=='('){
printf("%c",t[pos].opt); for(int i=1;i<=t[pos].num;i++)
{if(t[pos].opt=='('&&t[pos].num==2&&i==2) printf("I"); output(t[pos].to[i],on);}
// 因为没存 I 和 ),所以要判一下
if(t[pos].opt=='(') printf(")");
} if(t[pos].opt=='A'){printf("A%c",t[pos].var); output(t[pos].to[1],t[pos].var==repl?0:on);}
}
int main(){
// freopen("1.in","r",stdin);
scanf("%d",&T); while(T--){
para.clear(); memset(a,'\000',sizeof(a)); for(int i=1;i<=cnt;i++) t[i].num=0;
cin>>a; n=strlen(a); scanf("%d",&m); for(int i=1;i<=m;i++){
cin>>ch>>x; para[ch]=x; // 存函数和谓词的参数个数
} now=0; cnt=1; build(1,0); scanf("%d",&Q); while(Q--){
cin>>trans>>repl; if(check(1)) printf("Y\n"); else{printf("N\n"); continue;}
output(1,1); printf("\n");
}
} return 0;
}
/*
AxAyAz((Q(NQx)I(QxIQx))IQy)
*/