uva12219公共表达式消除

题解(基于紫书)

写在前面:基于紫书给出的思路,自己动手写的代码,运行时间700ms

大致思路

O(n)时间建表达式树;一次dfs:map查重、标号;一次dfs打印答案

讲解

因为按照书上的建树代码,会超时,dfs部分较简单,所以主讲建树。

O(n)时间建表达式树

借鉴书上经验,用一个变量ord记录“经过”的括号,遇到左括号"(" 则加一,遇到右括号")" 则减一。由于除了根结点,所有结点都有父亲,数组record记录ord位置上的结点序号,则对于当前结点,record[ord-1]即为父结点——因为子结点相对父结点被一对括号包裹,即他们相对父结点 “多出”一个左括号。初始化记录左右子的数组为0,当存在ord时,(除了根节点)ord-1必是ord的父结点(没有子结点的结点ord一定不会在后面产生它的子代ord+1,即使后面出现ord+1这个序号,此时的ord已经不是上文提到的那个ord了),至于ord-1是左子还是右子,由于有子代必有两个子代(依题意得),则那个是空的就填那个。
书上的例子 / 输入样例2

小细节

getchar实现一个一个读取一行输入,因为结点名字长度在1~4字符,不定长,需要根据后面出现"(", ")", "," 来判断一个结点读取完毕,故用一个变量writing标记“第一次读取完毕,”且需要特判只有一个结点的情况(即只有根结点)

关于map

我的key是一个struct,即书上的“用根的名字和左右子结点编号表示”,需要注意为struct重载小于号,否则判重(count)时会出错       

关于dfs

由于map查重要看整颗子树(即先看子代,子代看完了再看父代),dfs标号又是从父到子,矛盾,一次dfs只能标号,需要再用一次dfs打印

第一次dfs先顺着标下去(cnt),子代查完重再查父代,即逆着修改。ans保存了最终的正确序号:不重复的保存的是序号,重复的保存的是它的模板的序号,那么第二次dfs打印时,再维护一个序号标记,对比ans,对得上的打印结点的名字,对不上说明前一次dfs标记为重复,打印模板的序号即可。

AC代码

#include <cstdio>
#include <vector>
#include <cstring>
#include <map>

using namespace std;

const int maxn=50000+3;

int lch[maxn],rch[maxn];// children tree
int nc;// order of node
int ans[maxn];
int cnt;// final order counter

struct state{
    char nm[5];
    int chi[2];
    state(const char* str,const int* p){
        memcpy(nm,str, sizeof(char)*5);
        memcpy(chi,p, sizeof(int)*2);
    }
    bool operator < (const state& rhs) const {
        int cmp= strcmp(nm,rhs.nm);
        return  cmp<0|| (cmp==0 && chi[0]<rhs.chi[0]) ||
        (cmp==0 && chi[0]==rhs.chi[0] && chi[1]<rhs.chi[1]);
    }
};

map<state,int>node;

char op[maxn][5];

int record[maxn/2+3];// order
void build(){
    char c;
    char name[5];
    int len=0;
    int ord=1;// bracket
    bool writing;
    record[0]=0;
    bool single=true;// special judge: single input
    while ((c=getchar())!='\n'){
        if(c!='(' && c!=')' && c!=',') {
            name[len++]=c;
            writing=true;
            continue;
        }
        if(writing) {
            writing=false;
            single=false;
            name[len]='\0';
            len=0;
            int u=++nc;
            memcpy(op[u],name, sizeof(char)*5);
            int fa=record[ord-1];
            record[ord]=u;
            if(lch[fa]==0) lch[fa]=u;
            else rch[fa]=u;
        }
        switch (c) {
            case '(':++ord;break;
            case ')':--ord;break;
            default:continue;
        }
    }
    if(single) {
        name[len]='\0';
        memcpy(op[1],name, sizeof(char)*5);
    }
}

int dfs(int ord){
    if(ord==0) return 0;
    ans[ord]=++cnt;
    char* root=op[ord];
    int chi[2];
    chi[0]= dfs(lch[ord]);
    chi[1]= dfs(rch[ord]);
    state st(root,chi);
    if(node.count(st)) {
        cnt=ans[ord]-1;// no count
        ans[ord]=node[st];
    }else node[st]=ans[ord];

    return ans[ord];
}

void _print(int ord,int& f_ord) {
    if(f_ord==ans[ord]) {
        printf("%s",op[ord]);
        ++f_ord;// next node

        if(lch[ord]==0) return;
        else {
            printf("(");
            _print(lch[ord],f_ord);
            printf(",");
            _print(rch[ord],f_ord);
            printf(")");
        }
    }
    else {
        printf("%d",ans[ord]);
        return;
    }
}

int main(){
    int T;
    scanf("%d",&T);
    getchar();// empty character
    while (T--){
        // initialize
        node.clear();
        nc=0;// order: root-lch-rch; and start from no.1
        memset(ans,0, sizeof(ans));
        memset(lch,0, sizeof(lch));
        memset(rch,0, sizeof(rch));

        build();

        cnt=0;
        dfs(1);

        int f_ord=1;
        _print(1,f_ord);
        printf("\n");
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值