触手不及(巴科斯范式求表达式树)

本题为学军神犇 cxt 出的神题。

题意

为了避免流露出自己的感情伤害别人, 小 M.M.T. 决定通过一个表达式来传递心意.

给出一个等式.

等式左边是一个 \(int\) 范围内的数, 等式右边是一个合法的 c++ 表达式.

例如:\(233 = 66 ? 4 ? 31\)

保证等式右边只包含数字 \(x (x ∈ [0, p),p\) 是给定的质数\()\), 加号, 减号, 乘号, 除号, 左右括号.

保证等式中没有任何空格,tab 等不可见字符. 而且保证合法。

但是遗憾的是, 因为一些原因, 该等式不保证成立.

于是, 小 M.M.T. 希望知道, 在模 \(p\) 意义下, 她的表达式的每个数字 \(x\) ,需要变成多少才能使等式成立.

保证原不等式不存在除 \(0\), 你需要保证把数字变成 \(x\) 之后等式仍然不会除 \(0\).

如果无论 \(x\) 取多少都不能使等式成立, 则输出 \(No~Solution\) .

如果无论 \(x\) 取多少都能使等式成立, 则输出 \(-1\) .

\(len,p \le 5 \times 10^6\)

题解

首先很显然的根据给出的中缀表达式来求出二叉表达式树,如何求呢?

其实可以直接递归模拟这个过程。

这就是著名的 BNF(巴科斯范式) ,一种用递归的思想来表述计算机语言符号集的定义规范。

以下函数的名称全部参考自 BNF

  1. 首先最外面一层是由很多 \(+,-\) 号构成的优先级最低的符号,我们最外层处理这个,称为 \(expr\) (表达式)。
  2. 然后会接下来会分成很多个子表达式,每个最终会代表成一个值,我们把处理这单独一串值称为 \(term\) (相)。
  3. 每个 $term $ 是可能由 \(0\) 个,甚至多个 \(* ,/\) 连接一些数成的表达式,我们接下来就需要求那些数叫 \(factor\) (因子)
  4. 然后 \(factor\) 就会分成两种情况,要么为单独一个数(求单个数的为 \(digit\) ),要么就是以括号开头,然后继续是一个完整的表达式 \(expr\) 。

就这样不断递归处理,就可以处理出这颗二叉树了。

说起来似乎很玄学,如果看过代码后,就应该会觉得很好理解了。

整体思路就是,\(expr\) 实际上就是很多 \(term\) 用 \(+,-\) 连接起来的表达式。同理 \(term\) 就是很多 \(factor\) 用 \(*\) 号连接起来的乘积,\(factor\) 要么是个 \(digit\) 要么是个用 \(()\) 包起来的 \(expr\) 。

然后处理出这颗表达式二叉树后,就很好做了,我们令 Dfs(o, val) 表示 \(o\) 这颗子树(表达式)需要满足等式成立需要的改变成 \(val\) 。

递归下去处理,每次用四则运算的逆运算实现就行了。(此处需要预处理每个子树本来代表的值)

注意,叶子就是最后的数字。

然后有几个特殊情况。

  1. 当前区间运算为 \(*\) ,对于其中一颗子树来说,另一颗子树的值为 \(0\) ,有两种情况。

    • \(val \not = 0\) ,怎么改变都不能成立,那么那颗子树内所有点都为 \(No~Solution\) 。
    • \(val = 0\) ,怎么改变都可以成立,那么那颗子树内所有点都为 \(-1\) 。
  2. 当前区间运算为 \(/\) ,有两种特殊情况。
    • \(val = 0\) 并且左儿子值 \(\not = 0\) ,那么右儿子整个无解。
    • \(val \not = 0\) 并且左儿子值 \(= 0\) ,那么右儿子也是无解。

标程少判了最后一个情况,数据出锅了,差评。

代码

其实还是挺好 写的。

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)

using namespace std;

inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}

inline int read() {
    int x = 0, fh = 1; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
    return x * fh;
}

void File() {
#ifdef zjp_shadow
    freopen ("expression.in", "r", stdin);
    freopen ("expression.out", "w", stdout);
#endif
}

const int N = 5e6 + 1e3, Maxn = N << 1;

int Mod, Inv[N]; char str[N];

#define ls(o) ch[o][0]
#define rs(o) ch[o][1]

inline int Add(int a, int b) { return (a += b) >= Mod ? a - Mod : a; }

namespace Expression {

    char *Head;

    int Size, ch[Maxn][2], val[Maxn], opt[Maxn];

    inline void Push_Up(int o) {
        if (opt[o] == 0) val[o] = Add(val[ls(o)], val[rs(o)]);
        if (opt[o] == 1) val[o] = Add(val[ls(o)], Mod - val[rs(o)]);
        if (opt[o] == 2) val[o] = 1ll * val[ls(o)] * val[rs(o)] % Mod;
        if (opt[o] == 3) val[o] = 1ll * val[ls(o)] * Inv[val[rs(o)]] % Mod;
    }

    inline int Digit();
    inline int Factor();
    inline int Term();
    inline int Expr();

    inline int Digit() {
        int res = 0;
        for (; isdigit(*Head); ++ Head) res = (res * 10) + (*Head ^ 48) ;
        return res;
    }

    inline int Factor() {
        int Node = 0;
        if (*Head == '(')
            ++ Head, Node = Expr(), ++ Head;
        else
            val[Node = ++ Size] = Digit();
        return Node;
    }

    inline int Term() {
        int Last = Factor(), Node = Last;
        while (*Head == '*' || *Head == '/') {
            opt[Node = ++ Size] = 2 + (*Head ++ == '/');
            ls(Node) = Last; rs(Node) = Factor();
            Push_Up(Last = Node);
        }
        return Node;
    }

    inline int Expr() {
        int Last = Term(), Node = Last;
        while (*Head == '+' || *Head == '-') {
            opt[Node = ++ Size] = (*Head ++ == '-');
            ls(Node) = Last; rs(Node) = Term();
            Push_Up(Last = Node);
        }
        return Node;
    }

    void Print(int o, const char* ans) {
        if (!ls(o) && !rs(o))
            return (void) puts(ans);
        Print(ls(o), ans); Print(rs(o), ans);
    }

    void Dfs(int o, int Val) {
        if (!ls(o) && !rs(o))
            return (void) printf ("%d\n", Val);
        if (opt[o] == 0) {
            Dfs(ls(o), Add(Val, Mod - val[rs(o)]));
            Dfs(rs(o), Add(Val, Mod - val[ls(o)]));
        }
        if (opt[o] == 1) {
            Dfs(ls(o), Add(Val, val[rs(o)]));
            Dfs(rs(o), Add(val[ls(o)], Mod - Val));
        }
        if (opt[o] == 2) {
            For (dir, 0, 1)
                if (val[ch[o][dir ^ 1]])
                    Dfs(ch[o][dir], 1ll * Val * Inv[val[ch[o][dir ^ 1]]] % Mod);
                else
                    Print(ch[o][dir], Val ? "No Solution" : "-1");
        }
        if (opt[o] == 3) {
            Dfs(ls(o), 1ll * Val * val[rs(o)] % Mod);
            if (Val && val[ls[o]])
                Dfs(rs(o), 1ll * val[ls(o)] * Inv[Val] % Mod);
            else
                Print(rs(o), "No Solution");
        }
    }

    char Sign[4] = {'+', '-', '*', '/'};
    void Out(int o) {
        if (!o) return ;
        if (!ls(o) && !rs(o))
            return (void) printf (" %d ", val[o]);
        Out(ls(o));
        putchar (Sign[opt[o]]);
        Out(rs(o));
    }

};

void Solve() {
    using namespace Expression;
    Head = str;
    int Base = Digit() % Mod; ++ Head;
    int root = Expr();
    Dfs(root, Base);
}

int main () {

    File();

    read(); Mod = read(); scanf ("%s", str);

    Inv[0] = Inv[1] = 1;
    For (i, 2, Mod - 1) Inv[i] = 1ll * Inv[Mod % i] * (Mod - Mod / i) % Mod;
    Solve();

    return 0;
}

原文地址:https://www.cnblogs.com/zjp-shadow/p/9676609.html

时间: 2024-08-28 12:50:17

触手不及(巴科斯范式求表达式树)的相关文章

扩充巴科斯范式(ABNF)

BNF:巴科斯范式ABNF(Augmented Backus-Naur Form):扩充巴科斯范式 ABNF是由第68号互联网标准(”STD 68″,大小写样式按照原文)定义的,也就是 RFC 5234,经常用于互联网工程任务组(IETF)通信协议的定义语言.RFC 5234 取代了 RFC 4234(取代了 RFC 2234). 学习ABNF:ABNF规则的介绍 获取RFC对应的ABNF文件:在 这里 输入RFC number就可以获得该RFC对应的ABNF文件了. ABNF Parser G

巴科斯范式和sql语言

查询Mysql帮助文档,如何写SQL语句的时候,需要注意SQL语法,这里就需要知道BNF巴科斯范式. 巴科斯范式:BNF用于描述计算机语言.基本的规则如下: 尖括号<> 内包含的为必选项. 方括号[]   内包含的为可选项. 大括号{}  内包含的为可重复0至无数次的项. 竖线|       表示在其左右两边任选一项,相当于"OR"的意思. ::=         是被定义为的意思. Mysql的语法基本符合巴科斯范式,但有一些不同,如下: {} 表示必选项, ,... 表

BNF 和 ABNF 扩充巴科斯范式 了解

BNF 巴科斯范式(BNF: Backus-Naur Form 的缩写)是由 John Backus 和 Peter Naur 首先引入的用来描述计算机语言语法的符号集.现在,几乎每一位新编程语言书籍的作者都使用巴科斯范式来定义编程语言的语法规则. 在BNF中,双引号中的字("word")代表着这些字符本身.而double_quote用来代表双引号. 在双引号外的字(有可能有下划线)代表着语法部分. < > : 内包含的为必选项. [ ] : 内包含的为可选项. { } :

BNF范式(巴科斯范式)简介

BNF 规定是推导规则(产生式)的集合,写为: <符号> ::= <使用符号的表达式> 这里的 <符号> 是非终结符,而表达式由一个符号序列,或用指示选择的竖杠'|' 分隔的多个符号序列构成,每个符号序列整体都是左端的符号的一种可能的替代.从未在左端出现的符号叫做终结符. 基本原理      BNF类似一种数学游戏:从一个符号开始(叫做起始标志,实例中常用S表示),然后给出替换前面符号的规则.BNF语法定义的语言只不过是一个字符串集合,你可以按照下述规则书写,这些规则叫

扩充巴科斯-瑙尔范式 ABNF简介

扩充巴科斯-瑙尔范式(ABNF)是一种基于巴科斯-瑙尔范式(BNF)的元语言,但它有自己的语法和派生规则.ABNF的原动原则是描述一种作为双向通信协议的语言. ABNF是由第68号互联网标准("STD 68",大小写样式按照原文)定义的,也就是 RFC 5234,经常用于互联网工程任务组(IETF)通信协议的定义语言.RFC 5234 取代了 RFC 4234(取代了 RFC 2234 http://oss.org.cn/man/develop/rfc/RFC2234.txt). 有关

Lambda表达式和表达式树

在C# 2.0中,通过方法组转换和匿名方法,使委托的实现得到了极大的简化.但是,匿名方法仍然有些臃肿,而且当代码中充满了匿名方法的时候,可读性可能就会受到影响.C# 3.0中出现的Lambda表达式在不牺牲可读性的前提下,进一步简化了委托. LINQ的基本功能就是创建操作管道,以及这些操作需要的任何状态.这些操作表示了各种关于数据的逻辑,例如数据筛选,数据排序等等.通常这些操作都是用委托来表示.Lambda表达式是对LINQ数据操作的一种符合语言习惯的表示方式. Lambda表达式不仅可以用来创

16.C#初见Lambda表达式及表达式树(九章9.1-9.3)

在说明Lambda相关知识前,我们需要了解Lambda表达式常用于LINQ,那么我们来聊下LINQ. LINQ的基本功能就是创建操作管道,以及这些操作需要的任何状态.这些操作表示了各种关于数据的逻辑:如何过滤.如何排序以及如何将不同的数据源连接在一起,等等.执行委托只是LINQ的众多能力之一.为了富有效率地使用数据库和其他查询引擎,我们需要以一种不同的方式来表示管道中的各个操作.这种不同的方式就可以使用Lambda表达式来表现.下面分别使用委托(使用匿名函数)和Lambda表达式来作出同样的事情

前中后缀表达式以及表达式树

中缀表达式就是我们平时喜闻乐见的形式:二元运算符在中间,它的两个操作数在两侧: a + b * c + ( d * e + f ) * g 后缀和前缀表达式,顾名思义就是把运算符分别放在前面或者后面,注意没有括号,手工转换方法是按运算顺序添加括号,然后把相应的运算符置于相应的括号的前或后,如: ((a + ( b * c)) + (((d * e) + f) * g)) 放到前面有: +(+(a*(b c))*(+((*(d e))f)g)) 放到后面有: ((a(b c)*)+(((d e)*

Lambda表达式和Lambda表达式树

LINQ的基本功能就是创建操作管道,以及这些操作需要的任何状态. 为了富有效率的使用数据库和其他查询引擎,我们需要一种不同的方式表示管道中的各个操作.即把代码当作可在编程中进行检查的数据. Lambda表达式不仅可以用他们创建委托实例,而且C#编译器也能将他们转换成表达式树——用于表示Lambda表达式逻辑的一种数据结构.简言之——Lambda表达式用符号语言习惯的方法来表示LINQ数据管线中的操作. 作为委托的Lambda表达式 Lambda有特殊转换规则:表达式的类型本身并非委托类型,但它可