三瓶水问题

引子

有这么一道问题: 现有一瓶 10 ml 的水,和两个容量分别为 7 ml 和 3 ml 的空水瓶, 如何将 10 ml 的水平分成两个 5 ml ?

遇到一个问题,要看作一类问题,将这一类问题解决了,这一个问题就解决了, 以后遇到这样的每一个问题都解决了。

这个问题给出了具体的数值,我们可以替换为符号,然后用代数方法求解。 为了方便,将这类问题记为“三瓶水问题”。

数学模型

考虑一个二元组 (c,w)(c, w) 作为水瓶,其中 cc 是容量,ww 是水量。 有 3 个这样的瓶子,分别记为 B0=(c0,w0)B_0=(c_0, w_0)B1=(c1,w1)B_1=(c_1, w_1)B2=(c2,w2)B_2=(c_2, w_2), 满足

{c, wN; c0c0=c1+c2=w0+w1+w20w0c0, 0w1c1, 0w2c2 \left\{ \begin{aligned} &c,\ w\in \mathbb{N};\ c\neq 0\\ &c_0=c_1+c_2=w_0+w_1+w_2\\ &0\leq w_0\leq c_0,\ 0\leq w_1\leq c_1,\ 0\leq w_2\leq c_2 \end{aligned} \right.

倒水操作定义为关于 ww 的加减运算,例如从 B1B_1B2B_2 倒水,先计算 B2B_2 剩余容量, 如果大于等于 B1B_1 水量,则 B2B_2 水量加上 B1B_1 水量,然后 B1B_1 水量置零, 否则 B1B_1 水量减去 B2B_2 剩余容量,然后 B2B_2 水量置满。

程序模拟

以上数学模型可以表示为以下程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from dataclasses import dataclass

@dataclass
class Bottle:
    capacity: int
    water: int

    def __post_init__(self):
        # check the constraints
        pass

def pour(bottle1: Bottle, bottle2: Bottle) -> None:
    # pour from bottle1 to bottle2
    left_capacity = bottle2.capacity - bottle2.water
    if left_capacity >= bottle1.water:
        bottle2.water += bottle1.water
        bottle1.water = 0
    else:
        bottle1.water -= left_capacity
        bottle2.water = bottle2.capacity

数学归纳

从根本上解决问题还是需要数学方法,但是由于当前数学水平有限, 先从简单问题总结归纳得到猜想规律,以后有机会再做验证研究。

三瓶水问题可以拓展为分出 [0,c0][0,c_0] 之间任意整数的水量。 显然 0, c0, c1, c20,\ c_0,\ c_1,\ c_2 的水量一定可以得到,则 只需考虑 [1,c01][1,c_0-1] 之间的水量(不包含 c1, c2c_1,\ c_2 )。 另外易证,如果可以得到 xx 的水量,一定可以得到 c0xc_0-x 的水量,则 只需考虑 [1,c0/2][1,\lfloor c_0/2\rfloor] 之间的水量(不包含 c1, c2c_1,\ c_2 )。

简单例子

例 1

  • c0=5, c1=2, c2=3c_0=5,\ c_1=2,\ c_2=3 ,考虑 11,显然可以得到。
  • c0=5, c1=1, c2=4c_0=5,\ c_1=1,\ c_2=4 ,考虑 22,也可得到。

另外可以观察到,如果 c1c_1 或者 c2c_211,则所有水量一定可以得到。

例 2

  • c0=6, c1=2, c2=4c_0=6,\ c_1=2,\ c_2=4 ,考虑 1, 31,\ 3,无法得到。
  • c0=6, c1=3, c2=3c_0=6,\ c_1=3,\ c_2=3 ,考虑 1, 21,\ 2,无法得到。

例 3

  • c0=7, c1=2, c2=5c_0=7,\ c_1=2,\ c_2=5 ,考虑 1, 31,\ 3,可以得到。
  • c0=7, c1=3, c2=4c_0=7,\ c_1=3,\ c_2=4 ,考虑 1, 21,\ 2,可以得到。

思考与猜想

所有能得到的数都是通过 c0, c1, c2c_0,\ c_1,\ c_2 以及它们的和差之间的加减运算得到的。

  1. c0c_0 只能作为被减数,用于计算某一水量 xx 的补水量,即 c0xc_0-x
  2. c1c_1c2c_2 只能用大数 c1c_1 减去小数 c2c_2 得到 xx
  3. 如果 xx 是一个新值,则可以与 c2c_2 做减法,尝试与 c1c_1 做加法,否则结束。

新值即属于考虑集合但之前还未得到的值。

大胆的猜测:考虑集合内属于 c0, c1, c2c_0,\ c_1,\ c_2 最大公约数的值一定可以得到。 方法可能比较复杂,但一定存在,整体思路如上 3 个步骤。

该猜测在简单问题上已验证,但还未得到严格的数学证明。

附:解决引子问题

首先定义水瓶 B0=(10,10), B1=(7,0), B2=(3,0)B_0=(10, 10),\ B_1=(7, 0),\ B_2=(3, 0), 然后倒水,以下左边是方向,右边是完成状态:

B0B1B0=(10,3), B1=(7,7), B2=(3,0)B1B2B0=(10,3), B1=(7,4), B2=(3,3)B2B0B0=(10,6), B1=(7,4), B2=(3,0)B1B2B0=(10,6), B1=(7,1), B2=(3,3)B2B0B0=(10,9), B1=(7,1), B2=(3,0)B1B2B0=(10,9), B1=(7,0), B2=(3,1)B0B1B0=(10,2), B1=(7,7), B2=(3,1)B1B2B0=(10,2), B1=(7,5), B2=(3,3) \begin{align} &B_0\Rightarrow B_1&B_0=(10,3),\ B_1=(7,7),\ B_2=(3,0)\\ &B_1\Rightarrow B_2&B_0=(10,3),\ B_1=(7,4),\ B_2=(3,3)\\ &B_2\Rightarrow B_0&B_0=(10,6),\ B_1=(7,4),\ B_2=(3,0)\\ &B_1\Rightarrow B_2&B_0=(10,6),\ B_1=(7,1),\ B_2=(3,3)\\ &B_2\Rightarrow B_0&B_0=(10,9),\ B_1=(7,1),\ B_2=(3,0)\\ &B_1\Rightarrow B_2&B_0=(10,9),\ B_1=(7,0),\ B_2=(3,1)\\ &B_0\Rightarrow B_1&B_0=(10,2),\ B_1=(7,7),\ B_2=(3,1)\\ &B_1\Rightarrow B_2&B_0=(10,2),\ B_1=(7,5),\ B_2=(3,3)\\ \end{align}
Built with Hugo
Theme Stack designed by Jimmy