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;
}