很久之前的一场比赛有一道判断前缀表达式是否永真式的题,虽然当时用40分钟1A了,但是下来看了一下别人的代码,感觉自己的好low啊。。
当时的代码有1.9k,而且是对每个前缀运算符,分别找其对应的运算式,再递归下去的,复杂度也捉鸡:
1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 #include<cstring> 5 #include<algorithm> 6 #include<cmath> 7 using namespace std; 8 9 char s[100]; 10 struct node { 11 char type; 12 node *ch[2]; 13 node() { 14 ch[0] = ch[1] = NULL; 15 } 16 }pool[100000],*root; 17 int tot,ans,kind[150]; 18 bool have[150],value[150]; 19 char nex[150]; 20 21 node *build(int l,int r) 22 { 23 if(s[l]=='N') { 24 node *t = &pool[++tot]; 25 t->type = 'N'; 26 t->ch[0] = build(l+1,r); 27 return t; 28 } 29 if(kind[s[l]]==2) { 30 int req = 1,now = 0,i; 31 for(i = l+1; i<=r; i++) { 32 if(kind[s[i]]==2) req++; 33 else if(kind[s[i]]==1) now++; 34 if(now==req) { 35 node *t = &pool[++tot]; 36 t->ch[0] = build(l+1,i); 37 t->ch[1] = build(i+1,r); 38 t->type = s[l]; 39 return t; 40 } 41 } 42 } 43 if(l!=r) cout << "fuck"; 44 node *t = &pool[++tot]; 45 t->type = s[l]; 46 return t; 47 } 48 49 void preset() 50 { 51 kind['A'] = kind['K'] = kind['C'] = kind['E'] = 2; 52 kind['s'] = kind['p'] = kind['q'] = kind['r'] = kind['t'] = 1; 53 kind['N'] = 3; 54 nex['p'] = 'q'; 55 nex['q'] = 'r'; 56 nex['r'] = 's'; 57 nex['s'] = 't'; 58 nex['t'] = 0; 59 } 60 61 bool cal(node *t) 62 { 63 if(kind[t->type]==1) return value[t->type]; 64 if(t->type=='N') return !cal(t->ch[0]); 65 if(t->type=='K') 66 return cal(t->ch[0]) & cal(t->ch[1]); 67 if(t->type=='A') 68 return cal(t->ch[0]) | cal(t->ch[1]); 69 if(t->type=='C') 70 return (!cal(t->ch[0])) | cal(t->ch[1]); 71 if(t->type=='E') 72 return cal(t->ch[0])==cal(t->ch[1]); 73 } 74 75 void dfs(char x) 76 { 77 if(!ans) return; 78 if(!x) { 79 if(!cal(root)) ans = 0; 80 return; 81 } 82 if(have[x]) { 83 value[x] = 0; 84 dfs(nex[x]); 85 value[x] = 1; 86 dfs(nex[x]); 87 } 88 else dfs(nex[x]); 89 } 90 91 int main() 92 { 93 preset(); 94 while(1) { 95 scanf("%s", s+1); 96 if(s[1]=='0') break; 97 int l = strlen(s+1); 98 tot = 0; 99 root = build(1,l); 100 memset(have, 0, sizeof(have)); 101 for(int i = 1; i<=l; i++) 102 if(kind[s[i]]==1) 103 have[s[i]] = 1; 104 105 ans = 1, dfs('p'); 106 if(ans) printf("tautology\n"); 107 else printf("not\n"); 108 } 109 return 0; 110 }
后来知道了前缀表达式的正确写法。
假设一个式子是KAB的形式,K是运算符,A,B是子运算式。
假设A和B能够从前到后依次处理每个字符,那么KAB也可以先处理K,再从前到后处理A,再从前到后处理B,那么KAB也可以从前到后处理每个字符了。
初始条件很显然,用数学归纳法就ok了。
所以用一个指针pos,每次O(n)就可以处理了。
很简洁,只有900b:
1 #include <iostream> 2 #include <cstdio> 3 #include <string> 4 #include <cstring> 5 #include <algorithm> 6 #include <cmath> 7 using namespace std; 8 9 char s[10010]; 10 int pos,val,id[200]; 11 const int var[5] = {'p','q','r','s','t'}; 12 13 int solve() 14 { 15 int a,b,mem; 16 mem = ++pos; 17 if(s[pos]=='K' || s[pos]=='A' || s[pos]=='C' || s[pos]=='E') 18 { 19 a = solve()&1; b = solve()&1; 20 } 21 else if(s[pos]=='N') a = solve()&1; 22 switch(s[mem]) { 23 case 'K': return a & b; 24 case 'A': return a | b; 25 case 'N': return ~a; 26 case 'C': return (~a) | b; 27 case 'E': return (a==b)? 1:0; 28 default : return (val>>id[s[mem]])&1; 29 } 30 } 31 32 int main() 33 { 34 for(int i = 0; i<5; i++) id[var[i]] = i; 35 while(1) { 36 scanf("%s", s+1); 37 if(s[1]=='0') break; 38 int flag = 1; 39 for(val = (1<<5)-1; flag && val>=0; val--) { 40 pos = 0; 41 if(!(solve()&1)) flag = 0; 42 } 43 printf("%s\n", flag? "tautology" : "not"); 44 } 45 return 0; 46 }
这道题是解决了。
不过,我当时的第一反应就是:我去表达式怎么没有括号?。。诶他怎么没告诉我与或非的优先级?
不过按照这样的处理方式来看,确实是和优先级与括号没有任何关系。每一个表达式,无需知道运算优先级,就能唯一确定其运算顺序。
而对于一个中缀表达式,如果想必答任意的运算顺序,必须加入括号。(其实括号和运算符的优先级一样,是一种特殊的优先级;只要有括号,并不需要任何
运算符的优先级就可以表达任意运算顺序,因此不再区分括号和运算符的优先级)。因此,表达同样k个运算符k+1个运算数的表达式,前缀表达式比中缀表达式
需要的长度更小。这样看来,前缀表达式比中缀表达式包含更多的信息。
我们从同样长度的前缀和中缀表达式的个数来分析这个问题:对于k个运算符,k+1个运算数的表达式(均为二元运算),所有运算符的排列顺序和所有运算数的
排列顺序,前缀表达式和中缀表达式是一一对应的。但是,运算符和运算数的可以放置的位置却是不一样的。
例如,对于k=2的情况:
前缀表达式有o o x x x 和 o x o x x 两种排列方式(o表示运算符,x表示运算数)。
而中缀表达式永远只有 x o x o x 一种方式。
因此,前缀表达式确实是比中缀表达式包含了更多的信息。
至于k个运算符的前缀表达式的个数问题(认为所有运算符op和运算数var都是一样的):
一个前缀表达式,除了整个串以外,任意前缀的运算符数量大于等于运算数数量;整个串的op数等于var+1。
而且,任意一个满足上述条件的串都是一个前缀表达式。
使用数学归纳法很容易证明。
因此,一个前缀表达式和一个数字串是一一对应的。
而且,如果除去最后一个运算数,任意前缀的op数大于等于var数,且总op数等于var数。这不就是合法括号序列的个数么。
例:+ a - b c + * a b - c d
1 0 1 0 -1 1 2 1 0 1 0 -1
( ) ( ) ( ( ) ) ( )
把这表达式弄成一棵二叉树看的话,等价的表达是n个结点的二叉树个数。
这玩意就是Catalan数。有n个运算符的,通项是f(n) = C(2n,n)/(n+1)。
递推公式:f(n) = f(0)*f(n-1)+f(1)*f(n-2)+...+f(n-1)*f(0).