P1812 区间运算 题解

题意简析

给定若干个由区间组成的中缀表达式,求出它们的值。

区间运算规则如下:

  • 取相反数:

    \[-[a,b]=[-b,-a] \]

  • 加法:

    \[[a,b]+[c,d]=[a+c,b+d] \]

  • 减法:

    \[[a,b]-[c,d]=[a-d,b-c] \]

  • 乘法:

\[[a,b] * [c,d]=\min(ac,ad,bc,bd),\max(ac,ad,bc,bd)] \]

  • 除法:

\[[a,b] \div [c,d]=[\min(\frac{a}{c},\frac{a}{d},\frac{b}{c},\frac{b}{d}),\max(\frac{a}{c},\frac{a}{d},\frac{b}{c},\frac{b}{d})] \]

\(c,d \neq 0\)

思路解析

根据题意,我们很容易写出以下代码:

namespace RANGE {
const long double Infinity_Range = 1e18;
const long double _Infinity_Range = -1e18;
struct Range {
    long double l, r;
    Range operator+(const Range& b) const {
        return Range{l + b.l, r + b.r};
    }
    Range operator-(const Range& b) const {
        return Range{l - b.r, r - b.l};
    }
    Range operator*(const Range& b) const {
        return Range{min(l * b.l, l * b.r, r * b.l, r * b.r), max(l * b.l, l * b.r, r * b.l, r * b.r)};
    }
    Range operator/(const Range& b) const {
        if (!b.l || !b.r) {
            return Range{_Infinity_Range, Infinity_Range};
        }
        return Range{max(l / b.l, l / b.r, r / b.l, r / b.r), min(l / b.l, l / b.r, r / b.l, r / b.r)};
    }
    Range operator!() const {
        return Range{-r, -l};
    }
};
}  // namespace RANGE

using namespace RANGE;

但是我们仅仅掌握了它的运算规则,那到底从哪着手运算呢?那么多括号该怎么处理呢?

表达式求值

522X522/graph_(7).png

如图,这是一棵表达式 \((a-b)*c\) 的表达式树,可以发现,叶子节点存储数值,非叶子节点存储运算符号。

前缀表达式(波兰式)

波兰表示法,是波兰数学家、逻辑学家、哲学家扬·武卡谢维奇,于 20 世纪 20 年代,提出的不需用括号表示一个式子的方法,简洁明了,在计算机科学中有广泛应用。

是表达式树的前序遍历。

例子中的前缀表达式(波兰式)即为:

\[*-abc \]

中缀表达式

是表达式树的中序遍历,也是我们最常用的。有时候需要添加括号来表示运算的先后。

3840X2160/运算优先级.png

例子中的中缀表达式即为:

\[(a-b)*c \]

后缀表达式(逆波兰式)

逆波兰表示法,是波兰数学家、逻辑学家、哲学家扬·武卡谢维奇,于 20 世纪 20 年代,提出的不需用括号表示一个式子的方法,简洁明了,在计算机科学中有广泛应用。(没错又是他

是表达式树的后序遍历。

例子中的后缀表达式(逆波兰式)即为:

\[ab-c* \]

样例分析

前面讲了这么多和这道题有什么关系呢?

-[-3,5] 
[3,5]+[-10,1] 
[3,5]-[-10,1] 
[3,5]*[-10,1] 
(([3,5]/[-10,-0.1])/-[2,2]) 

样例中的四个表达式可以画作四颗表达式树:

522X522/graph_(2).png

522X522/graph_(3).png

522X522/graph_(4).png

522X522/graph_(5).png

522X522/graph_(6).png

我们在学习树的遍历的时候,可以用递归来解决,那么我们求类似的问题时,可不可以也用递归实现呢?显然是可以的。

更加深入地,我们想到递归也可以用来表示,边存边算。具体地:

  • 若读入的为左括号,将新元素压入栈,深度增大。
  • 若读入的为右括号,计算结果,把计算过的元素出栈,把结果入栈,深度减少。
  • 若读入的运算符号运算优先级小于等于栈顶的运算符号,那么这时候就要从左往右正常运算,把计算过的元素出栈,把结果入栈,深度减少。

代码实现

对于出现除数区间为 \(0\) 的,我们可以把左右区间变成无穷大特判,也可以使用异常捕获语句。

Python

eval() 是 Python 中的一个强大的函数,它可以将字符串作为代码执行。

定义一个 Interval 类,并重载运算符来实现区间运算。

class Interval:
    def __init__(self, min_val, max_val):
        # 确保 min_val <= max_val
        self.min = min(min_val, max_val)
        self.max = max(min_val, max_val)

    def __neg__(self):
        return Interval(-self.max, -self.min)

    def __add__(self, other):
        return Interval(self.min + other.min, self.max + other.max)

    def __sub__(self, other):
        return Interval(self.min - other.max, self.max - other.min)

    def __mul__(self, other):
        candidates = [
            self.min * other.min,
            self.min * other.max,
            self.max * other.min,
            self.max * other.max
        ]
        return Interval(min(candidates), max(candidates))

    def __truediv__(self, other):
        if other.min <= 0 <= other.max:
            raise ZeroDivisionError("Division by zero")
        candidates = [
            self.min / other.min if other.min != 0 else float('inf'),
            self.min / other.max if other.max != 0 else float('inf'),
            self.max / other.min if other.min != 0 else float('inf'),
            self.max / other.max if other.max != 0 else float('inf')
        ]
        return Interval(min(candidates), max(candidates))

    def __repr__(self):
        return f"[{self.min:.3f},{self.max:.3f}]"

def parse_interval(s):
    min_val, max_val = map(float, s.strip("[]").split(","))
    # 确保 min_val <= max_val
    return Interval(min_val, max_val)

def safe_eval(expression):
    # 替换区间表示为 Interval 对象
    while "[" in expression:
        start = expression.find("[")
        end = expression.find("]")
        interval_str = expression[start:end + 1]
        interval_obj = parse_interval(interval_str)
        expression = expression.replace(interval_str, f"Interval({interval_obj.min}, {interval_obj.max})", 1)
    return eval(expression, {"__builtins__": None}, {"Interval": Interval})

def main():
    import sys
    input = sys.stdin.read
    data = input().strip().split("\n")
    
    for line in data:
        try:
            result = safe_eval(line)
            print(result)
        except ZeroDivisionError:
            print("Division by zero")

if __name__ == "__main__":
    main()

后记

  1. 除法中如果除数的区间包含 \(0\),则输出 Division by zero
  2. \(l > r\) 时要交换 \(l\)\(r\)
  3. 考虑 \(0.0000\)\(-0.0000\) 的特殊情况。
posted @ 2025-08-02 22:30  TangyixiaoQAQ  阅读(20)  评论(0)    收藏  举报