zoukankan      html  css  js  c++  java
  • 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
    ");
    107         else printf("not
    ");
    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
    ", 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).

  • 相关阅读:
    [WC2011]最大XOR和路径 线性基
    线段树分裂合并
    [NOIp2016]天天爱跑步 线段树合并
    CF1111E Tree 树链剖分,DP
    [NOI2016]区间 线段树
    [IOI2018] werewolf 狼人 kruskal重构树,主席树
    [CQOI2012]组装 贪心
    [ONTAK2010]Peaks kruskal重构树,主席树
    [NOI2018]归程 kruskal重构树
    kruskal重构树
  • 原文地址:https://www.cnblogs.com/liuaohan/p/5694741.html
Copyright © 2011-2022 走看看