POJ3383 表达式解析树|集合

一道关于集合操作的POJ题目,要求根据给定的集合运算判断方程是否有解。通过解析表达式树,使用深度优先搜索检查每个元素的取舍是否满足方程。若所有未知变量都有解,则方程有解。主要难点在于解析表达式和构造有效的表达式树。
摘要由CSDN通过智能技术生成

poj.org/problem?id=3383

这题不难,但是AC比例也很低

Total Submissions: 101Accepted: 14Special Judge

大致题意是:

1. 定义4个集合操作,有不同的优先级,给定一个方程,和已知的一些变量的值 (每个变量都是一个集合),包括全集,判断方程中的未知变量是否有解

思路

集合变量是否有解比较简单,只要判断对于全集中的任一个元素,如何这个方程左边和右边的计算结果可以同时包含这个元素,或是同时不包含这个元素,那么这个元素就可以满足方程;如果某个元素的取舍无法满足方程,则说明方程无界;

对于未知变量的解也很简单,对已任意元素,如果不包含这个元素就可以满足方程,那么就可以按照左右两边的计算结果都不包含该元素来贪心赋值即可

所以问题就归结为

1. 解析方程字符串为二元运算表达式数

2. 对于任意元素, 依次dfs检查以当前节点为根的表达式数是否可以包含/不包含这个元素,如果某个节点既不能包含也不能不包含某个元素,这该表达式树无效

3. 对于有效的表达式树,再次dfs设置那些未知变量是否包含这个元素

实现

代码量比较大,感觉这题主要是代码能力,如何解析表达式应该算是基本功吧 (去年写了一个基于公式的k8s HPA扩展controller,写起来还是比较轻松的)

对于又优先级的表达式,只需要维护一个优先级递增的栈即可, 这里只有四种操作,所以栈的最大长度是4

int parse(char *p, int *pi) {
    int i =*pi, j, k;
    char op[8], oc=0, co, cr, oo, or, bbb;
    int nn[8], nc=0, n1, n2, n;
    while(1) {
        while(p[i]&&(p[i]==' '||p[i]=='\t')) i++; if (p[i]==0) break;     
        if (p[i]==')') break;
        if (p[i]=='(') {
            i++;  n=parse(p, &i); if (p[i]!=')') assert(0); i++;
            nn[nc++]=n;
        } else if (p[i]=='+'||p[i]=='-'||p[i]=='*'||p[i]=='^') {
            co=p[i];  cr=rank[co];
            while(oc) {
               oo=op[oc-1]; or=rank[oo]; if (or<cr) break;
               n1=nn[--nc]; n2=nn[--nc];
               n=alloc(oo, n2, n1); nn[nc++]=n;
               oc--;
            }
            op[oc++]=co;
            i++; continue;
        } else {
            // var
            j=i; while(vmk[p[j]]) j++;
            bbb=p[j]; p[j]=0;
            k = addn(p+i); p[j]=bbb;
            n=alloc(-1, k, -1);
            nn[nc++]=n;
            i=j;
        }
    }
    while(oc) {
        oc--; oo=op[oc];
        n1=nn[--nc]; n2=nn[--nc];
        n=alloc(oo, n2, n1); nn[nc++]=n;
    }
    *pi=i;
    if (nc==0) return -1;
    return nn[0];
}

检查表达式树是否可以包含/不包含某个元素, 这里记录了计算结果,因为在最后赋值的时候还会用到

char check(int r, int k) {
    int i;
    char ml, mr, mm;
    if (obs[r]==-1) {
        i = lhs[r];
        if (nmk[i]) {
            if (smk[i][k/32]&(1<<(k%32))) dpm[r]=2;
            else dpm[r]=1;
        } else dpm[r]=3;
    } else {
        ml=check(lhs[r], k);
        mr=check(rhs[r], k);
        mm=0;
        if (obs[r]=='+') {
            if (ml&mr&1) mm|=1;
            if (((ml&2)&&mr) || ((mr&2)&&ml)) mm|=2;
        } else if (obs[r]=='*') {
            if (((ml&1)&&mr) || ((mr&1)&&ml)) mm|=1;
            if ((ml&mr)&2) mm|=2;
        } else if (obs[r]=='-') {
            if (((ml&1)&&mr)||((ml&2)&&(mr&2))) mm|=1;
            if ((ml&2)&&(mr&1)) mm|=2;
        } else if (obs[r]=='^') {
            if (((ml&1)&&(mr&1)) || ((ml&2)&&(mr&2))) mm|=1;
            if (((ml&1)&&(mr&2)) || ((ml&2)&&(mr&1))) mm|=2;
        } else assert(0);
        dpm[r]=mm;
    }
    return dpm[r]
}

赋值时,一旦确认方程是有效的,那就直接找可行路径即可

void setv(int r, int m, int k) {
    int i;
    char ml, mr, mm;
    if (obs[r]==-1) {
        i = lhs[r];
        if (nmk[i]==0&&m) smk[i][k/32]|=(1<<(k%32));
    } else {
        ml=dpm[lhs[r]];
        mr=dpm[rhs[r]];
        if (obs[r]=='+') {
            if (m==0) {
                setv(lhs[r], 0, k);
                setv(rhs[r], 0, k);
            } else {
                if ((ml&2)&&mr) {
                    setv(lhs[r], 1, k);
                    if (mr&1) setv(rhs[r], 0, k);
                    else setv(rhs[r], 1, k);
                } else if ((mr&2)&&ml) {
                    setv(rhs[r], 1, k);
                    if (ml&1) setv(lhs[r], 0, k);
                    else setv(lhs[r], 1, k);
                }
            }
        } else if (obs[r]=='*') {
            if (m==0) {
                if ((ml&1)&&mr) {
                    setv(lhs[r], 0, k);
                    if (mr&1) setv(rhs[r], 0, k);
                    else setv(rhs[r], 1, k);
                } else {
                    setv(rhs[r], 0, k);
                    if (ml&1) setv(lhs[r], 0, k);
                    else setv(lhs[r], 1, k);
                }
            } else {
                setv(lhs[r], 1, k);
                setv(rhs[r], 1, k);
            }
        } else if (obs[r]=='-') {
            if (m==0) {
                if ((ml&1)&&mr) {
                    setv(lhs[r], 0, k);
                    if (mr&1) setv(rhs[r], 0, k);
                    else setv(rhs[r], 1, k);
                } else {
                    setv(lhs[r], 1, k);
                    setv(rhs[r], 1, k);
                }
            } else {
                setv(lhs[r], 1, k);
                setv(rhs[r], 0, k);
            }
        } else if (obs[r]=='^') {
            if (m==0) {
                if ((ml&1)&&(mr&1)) {
                    setv(lhs[r], 0, k);
                    setv(rhs[r], 0, k);
                } else {
                    setv(lhs[r], 1, k);
                    setv(rhs[r], 1, k);
                }
            } else {
                if ((ml&1)&&(mr&2)) {
                    setv(lhs[r], 0, k);
                    setv(rhs[r], 1, k);
                } else {
                    setv(lhs[r], 1, k);
                    setv(rhs[r], 0, k);
                }
            }
        } else assert(0);
    }
}

这题的思路是比较简单的,只是代码量有点大,刚开始写这题的时候我是满心抗拒的,折腾的2天才慢悠悠一点点写完.....基本上写完就1A了, 算是1A,因为有个变量名or在c++/g++里编译不过,compiler error了一次,目前排名第一

RankRun IDUserMemoryTimeLanguageCode LengthSubmit Time
123272023lddlinan424K110MSGCC7036B2022-02-25 19:52:10
223112867nwin1200K125MSG++2819B2021-11-23 00:05:11
312958087vjudge25648K172MSC++8551B2014-06-08 15:33:20
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值