小学生奥数倒水问题的数学模型与算法求解

1 问题描述

设有n个容器,容量分别为L1, L2, ..., Ln,初始情况下的水量状态向量为(W1, W2, ..., Wn)。要求的目标水量状态向量为(D1, D2, ..., Dn)。求是否有一个倒水的操作序列作用与水量状态向量,使其从初始态变为目标态,要求不能借助其他工具,包括肉眼观察水位。

2 问题分析

2.1 倒水操作规则

我们可以分析得到,倒水操作序列中的每一个操作必为可逆操作,它必满足以下几种情况之一:

  • 源水杯原始水位为满,目标水杯操作后水位为满
  • 目标水杯原始水位为空,目标水杯操作后水位为满
  • 源水杯原始水位为满,源水杯操作后水位为空
  • 源水杯操作后水位为空,目标水杯原始水位为空

可以看出上述规则中,调换源水杯与目标水杯,规则不变,具有良好的对称性。为了描述更加清晰,我们设:

描述变量
源水杯原始水位src
源水杯操作后水位src’
目标水杯原始水位dst
目标水杯操作后水位dst’
源水杯容量Lsrc
目标水杯容量Ldst

如此上述规则可形式化表述为如下:
(src’ = 0 and src = Lsrc) or (src’ = 0 and dst = 0) or (dst’ = Ldst and dst = 0) or (dst’ = Ldst and src = Lsrc)
利用一些谓词逻辑的化简得到:
(src = Lsrc or dst = 0) and (dst' = Ldst or src' = 0)

2.2 倒水量规则

我们可以分析得到每次的倒水量为:min(源水杯水量, 目标水杯容量 - 目标水杯水量)

3 算法描述

求解过程(水量状态向量, 记录表):
    如果当前水量状态向量 = 目标向量:
        返回成功
    对于第i个水杯(i从1到n):
        对于第j个水杯(j从1到n):
            如果i != j 且 水杯i不为空 且 水杯j不为满:
                设delta为倒水量 = min(i水杯水量, j水杯容量 - j水杯水量)
                如果满足(src = Lsrc or dst = 0) and (dst' = Ldst or src' = 0)条件:
                    倒水
                    如果当前水量状态向量未出现过:
                        将该状态向量加入记录表中
                        递归求下一个操作
                    如果递归返回成功:
                        输出倒水操作
                    把水恢复回来
    如果都没成功(没找到可逆操作,或找到的可逆操作不可行):
        返回失败

4 程序源码

#include <iostream>
#include <vector>

std::vector<int> glasses {16, 9, 7};
std::vector<int> initial {16, 0, 0};
std::vector<long> visited {};

long tokenize(std::vector<int> waters) {
    if (waters.empty()) return 0;
    long token = waters[0];
    long weigh = 100;
    for (size_t i = 1; i < waters.size(); ++ i) {
        token += (weigh * waters[i]);
        weigh *= 100;
    }
    return token;
}

bool isVisited(long token) {
    for (auto v: visited) if (v == token) return true;
    return false;
}

bool reversable(std::vector<int> waters) {
    bool overFlag = true;
    for (size_t i = 0; i < glasses.size(); ++ i) {
        if (glasses[i] != initial[i]) overFlag = false;
    }
    if (overFlag) return true;
    for (size_t i = 0; i < waters.size(); ++ i) {
        if (waters[i] < glasses[i]) {
            int delta = glasses[i] - waters[i];
            waters[i] = glasses[i];
            for (size_t j = 0; j < waters.size(); ++ j) {
                if (j != i && waters[j] - delta >= 0 && 
                    (waters[j] == glasses[j] || delta == glasses[i])) {
                    int water_sav = waters[j];
                    waters[j] -= delta;
                    long token = tokenize(waters);
                    std::cout << token << std::endl;
                    if (!isVisited(token)) {
                        visited.push_back(token);
                        if (reversable(waters)) {
                            for (auto water: waters) {
                                std::cout << water << " ";
                            }
                            std::cout << std::endl;
                            return true;
                        }
                    }
                    waters[j] = water_sav;
                }
            }
            waters[i] = glasses[i] - delta;
        }
    }
    return false;
}

void fillWater() {
    reversable({8, 8, 0});
}

int main() {
    fillWater();
    return 0;
}

程序效果

程序效果截图

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值