1 条题解

  • 0
    @ 2025-8-24 22:54:28

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar 2022liaojianxiang
    小小蒟蒻一枚

    搬运于2025-08-24 22:54:28,当前版本为作者最后更新于2024-02-02 19:48:05,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    感谢洛谷把 GDKOI 放上来了!借这道题来庆祝我普及金奖!

    【GDKOI2024 普及组】EX 中缀表达式的题解

    在这里提出我的建议:

    • 这道题建议升紫,在模拟中还有数学参入,所以难度还是比较大的,希望建议能被采纳,谢谢!

    题目大意

    这是一道数学、模拟题,要求计算表达式的值并且按照它规定的优先级和结合的左右顺序进行处理并模 109+710^9+7 输出,还要判断表达式是否合法,符号只有 +*^

    前置芝士

    考场上的情况(不想看的可以跳过)

    先说一下我比赛时的情况:那时我非常智慧,看到这道题一眼模拟直接无脑开始狂打。像这种表达式求值的题很明显的做法是将中缀表达式转成后缀表达式,然后进行求值。因为打熟悉了中缀表达式转成后缀表达式,所以很快打完,并且 Error 判断的也比较好,最后一直过不了样例,甚至叫来老师问是不是样例出错了(因为样例有次方运算,我又把次方给进行了模运算,导致直接暴力算都是错的),最后也是不明不白地离开了考场。出来和同学讨论以后才发现次方运算的次方不可以直接模。结果还怀疑人家题出错了【捂脸】。最后居然还能拿没有次方运算的 1616 分。加上 Error1212 分(有两个没有判断到)。

    正解

    其实就是暴力,只不过细节有亿点点多。

    难点

    判断不合法的 Error 情况:

    首先判断不合法的中缀表达式有以下几种:

    • 出现了不是数字、+*^()的字符;

    • 括号不匹配;

    • 运算符没有数字在前表示优先级或者数字不在 11nn 的范围内;

    • 两个括号之间为空;

    • 每个字符没有对应运算的两个数字。

    将中缀表达式转成后缀表达式

    个人认为这是比较简单的一步,只要你学过中缀表达式转成后缀表达式。在这里还是讲一下步骤吧:

    我们用一个字符栈来维护这个过程。

    • 如果遇到数字,直接加入后缀表达式。

    • 如果遇到字符,先判断是否有优先级更高的字符在栈中,如果高就弹出并加入后缀表达式,直到遇到优先级比它低的符号。如果在弹的过程中遇到左括号,就不再弹出;

    • 如果遇到左括号,直接加入字符栈;

    • 如果遇到右括号,一直弹运算符直到左括号,同时也弹掉左括号;

    • 那怎么处理同优先级的左右运算呢?题目中确实有这样的规定。左结合就是从左往右运算,左边先加入的优先级更高,右结合代表从右往左运算,右边的优先级更高;

    • 如果字符栈里还有剩余就全部弹出即可。

    这时我们就得到了一个后缀表达式。

    将次方取模时的处理(乘方运算在取模意义下的取值)

    对于次方来说确实不好办的,不可以直接取模,所以这应该是最难的一个点了,我们需要用到一个数学推论:扩展欧拉定理。

    这里给出式子:

    $$a^b\equiv \begin{cases} a^{\left(b\ \bmod \ \varphi(p) \right)},&\gcd(a, p)=1 \\ a^b,& \gcd(a,p)\ne1,b<\varphi (p) \\ a^{\left(b\ \bmod \ \varphi(p) + \varphi(p) \right)},&\gcd(a, p)\ne1,b\ge \varphi (p) \end{cases} $$

    我们发现不能仅仅靠后缀表达式,我们需要建立一颗后缀表达式树,因为我们需要先算出 aa,然后根据 aa 是否与 pp 互质分类(pp 指的是当前的模数),然后再递归 bb 改变它的模数为 φ(p)\varphi(p)。对于每个数字或字符直接动态开点记录左右儿子即可。

    接下来的操作对于加法和乘法是比较简单的,我们直接将它们取模返回值即可。

    当我们遇到次方运算时,我们需要计算 aba^baa 是很容易得到的,直接递归左边的子树(aa 的子树下的值是可以直接 modp\mod p 的),得到 aa 的值以后我们看 gcd(a,p)\gcd(a,p) 是否为 11(是否互质)。

    如果互质也就是第一种情况就非常简单,求得 φ(p)\varphi (p) 后把它当成 bb 的子树的模数下传得到之后返回快速幂计算即可。

    发现不互质的情况还是很麻烦,需要判断 bbφ(p)\varphi (p) 的大小。但 bb 下到子树下可能还会多次改变模数从而改变 bb 原来有的值,所以我们为了判断 bb 的大小再预处理一遍整颗后缀表达式树,算出 φ(p)\varphi (p) 后每次在 bb 运算时及时判断它什么时候超过了 φ(p)\varphi (p),超过了我们记录一个数组 qq 的值为 1-1,没超过就直接记录 bb 的值,这样就可以对 bb 的大小分类,如果是 1-1 或者当前的 qq 值比当前的 φ(p)\varphi (p) 要大表示应该是上述第三种情况,否则已经得到了 bb 的值(在 qq 里面)直接运算即可。

    这样就能发现获得了 84pts84pts

    最后发现输入的数字还有可能是一个很多位数的数字,必须用高精度来存储,只用在刚开始的数字中计算即可(因为上传的时候会模数)。建议高精度用结构体存储,并只在表达式树下层叶子节点数字计算高精度时使用,上层取模后可以直接存储。

    代码有点小多,要坚持下去才能打败这种大模拟题!

    Code

    详见云剪贴板

    • 1

    信息

    ID
    9743
    时间
    2000ms
    内存
    512MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者