poj 3295 Tautology

很久之前的一场比赛有一道判断前缀表达式是否永真式的题,虽然当时用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 }
View Code

后来知道了前缀表达式的正确写法。

假设一个式子是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 }
View Code

 

这道题是解决了。

不过,我当时的第一反应就是:我去表达式怎么没有括号?。。诶他怎么没告诉我与或非的优先级?

不过按照这样的处理方式来看,确实是和优先级与括号没有任何关系。每一个表达式,无需知道运算优先级,就能唯一确定其运算顺序。

而对于一个中缀表达式,如果想必答任意的运算顺序,必须加入括号。(其实括号和运算符的优先级一样,是一种特殊的优先级;只要有括号,并不需要任何

运算符的优先级就可以表达任意运算顺序,因此不再区分括号和运算符的优先级)。因此,表达同样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).

posted @ 2016-07-23 20:15  lyxxx  阅读(229)  评论(0编辑  收藏  举报