原題敘述
題目描述
倒水問題 “fill A” 表示倒滿A杯,"empty A"表示倒空A杯,“pour A B” 表示把A的水倒到B杯並且把B杯倒滿或A倒空。
INPUT
輸入包含多組數據。每組數據輸入 A, B, C 數據範圍 0 < A <= B 、C <= B <=1000 、A和B互質。
OUTPUT
你的程序的輸出將由一系列的指令組成。這些輸出行將導致任何一個罐子正好包含C單位的水。每組數據的最後一行輸出應該是“success”。輸出行從第1列開始,不應該有空行或任何尾隨空格。
輸入樣例
2 7 5
2 7 4
輸出樣例
fill B
pour B A
success
fill A
pour A B
fill A
pour A B
success
題目重述
題目的字面意義比較簡單就是給兩個容量互質的杯子,一個是a,一個是b,使用這兩個杯子,倒出一杯c容量的水,且一直該問題是有解的。
解題思路
思路概述
該題目題意看起來非常簡單易懂,但第一次做可能先想到的並非使用BFS而是貪心,但經過嘗試兩種做法後,BFS的解法相對容易實現,思考邊界條件的難度較小,適合解題使用。因此該處敘述BFS解法:
隱視圖的核心思想就是將問題轉化爲BFS可以進行搜索的類型,並且找到各個狀態之間轉換關係和邊界條件。
題意轉換:將倒水的過程中兩個杯子的狀態視爲同一個struct,就可以將兩個杯子的問題轉換爲多個狀態的問題,題目的求解也可以轉換爲從初始狀態進行BFS搜索得到最終狀態(A or B=c)
狀態間的轉換關係:
(這裏我們用a,b,c代表杯子容量,A,B代表某個時間杯子的狀態)
狀態 | 採取的轉換行爲 |
---|---|
A>0 | 倒空A |
B>0 | 倒空B |
A<a | 倒滿A |
B<b | 倒滿B |
A< a&& B>0 && A+B<a | B倒向A,且倒不滿 |
A<a && B>0 && A+B>=a | B倒向A,將A倒滿 |
B<b && A>0 && A+B<b | A倒向B,且倒不滿 |
B<b && A>0 && A+B>=b | A倒向B,將B倒滿 |
BFS的邊界條件:
A=c || B=c
數據存儲
這個問題中,最重要的數據莫過於在倒水過程中,兩個狀態之間的轉換,爲了保證在BFS過程,每兩個狀態之間的轉換隻出現一次,從而保證找到的倒水次數一定最少,我們使用map<status,status>,來存儲兩個狀態之間的轉換行爲,在BFS到一個狀態後,我們只需要對這個轉換行爲做這樣的操作:
void refresh(status &s, status &t)
{
if ( from.find(t) == from.end() )
{ // 特判合法,加入隊列
from[t] = s;
Q.push(t);
}
}
就可以保證加入隊列的狀態是一個新的狀態,即這個轉換是合法的
因爲在每一次進行BFS轉換到一個狀態是都要進行一個合法性的判斷,我建議將這段代碼封裝成一個函數,會大大地增強代碼的可讀性。
map用不熟的小夥伴請移步:c++官方文檔
總結
一道使用BFS蠻有意思的題目,在找到方法之後就能夠輕鬆地打出代碼,碼力要求不高,但是需要你有思想。掌握BFS的核心也就是狀態的轉移和邊界條件,就能將複雜的問題轉換爲簡單的BFS板子。
改進點
無論BFS還是DFS說到底就是一種枚舉,要遍歷各種情況,但是貪心或者更好的算法能夠減少很多訪問點,節省時間,所以可能有比這種算法耗時更少的算法,但是從比賽解題的角度看,BFS的易於實現度仍然是在面對低時耗要求題目時比較好的一種實現方法。
問題源碼(c++)
#include<cstring>
#include<cstdio>
#include<iostream>
#include<map>
#include<queue>
using namespace std;
struct status
{
/* data */
int a,b;
bool operator<(const status &s)const{
if(a!=s.a) return a<s.a;
return b<s.b;
}
bool operator==(const status &s)const{
return a==s.a && b==s.b;
}
};
queue<status> Q;
map<status, status> from;
vector<status> ans;
/* 遞歸輸出方案 */
void print(status &p,int A,int B,int C)
{
ans.push_back(p);
while( from.find(p) != from.end() && (p.a!=0 | p.b!=0))
{
p=from[p];
ans.push_back(p);
}
for(int i=ans.size()-1;i>0;i--)
{
status start=ans[i];
status end=ans[i-1];
if(start.b==end.b)
{
if(start.a<A && end.a==A)
{
printf("fill A\n");
}
else if(start.a>0 && end.a==0)
{
printf("empty A\n");
}
}
else if(start.a==end.a)
{
if(start.b<B && end.b==B)
{
printf("fill B\n");
}
else if(start.b>0 && end.b==0)
{
printf("empty B\n");
}
}
else if(start.a<end.a && start.b>end.b)
{
printf("pour B A\n");
}
else if(start.a>end.a && start.b<end.b)
{
printf("pour A B\n");
}
}
printf("success\n");
}
void refresh(status &s, status &t)
{
if ( from.find(t) == from.end() )
{ // 特判合法,加入隊列
from[t] = s;
Q.push(t);
}
}
void bfs(int A, int B, int C)
{
// 起點, 兩杯水都空
status s,t; s.a=0; s.b=0;
Q.push(s);
while (!Q.empty())
{
// 取隊首
s = Q.front(); Q.pop();
// 特判到達終點
if (s.a == C || s.b == C) {
print(s,A,B,C); // 輸出方案
while(!Q.empty())
Q.pop();
from.clear();
ans.clear();
return;
}
// 倒空 a 杯的水
if (s.a > 0) {
t.a = 0; // 倒空
t.b = s.b;// b 杯不變
refresh(s, t);
}
// 同理,倒空 b 杯的水
if (s.b > 0) {
t.b = 0; // 倒空
t.a = s.a;// a 杯不變
refresh(s, t);
}
// a 杯未滿,續滿 a 杯
if (s.a < A)
{
// 續滿 a 杯
t.a = A;
t.b = s.b;
refresh(s, t);
}
// 同理,b 杯未滿,續滿 b 杯
if (s.b < B)
{
//續滿 b 杯
t.a = s.a;
t.b = B;
refresh(s, t);
}
if (s.b != 0 && s.a<A)
{
if (s.a + s.b <= A)
{
t.a = s.a + s.b;
t.b = 0;
refresh(s, t);
} else
{
t.a = A;
t.b = s.a + s.b - A;
refresh(s, t);
}
}
if (s.a != 0 && s.b<B)
{
if (s.a + s.b <= B)
{
t.a = 0;
t.b = s.a + s.b;
refresh(s, t);
} else
{
t.a = s.a + s.b - B;
t.b = B;
refresh(s, t);
}
}
}
printf("-1\n");
}
int main()
{
int a, b, c;
while(cin>>a>>b>>c)
{
bfs(a, b, c);
}
return 0;
}/* 26 29 11 */