分紅酒 - 藍橋杯 - 已更新

歡迎訪問我的新博客:http://www.milkcu.com/blog/

原文地址:http://www.milkcu.com/blog/archives/1395211740.html

引言

這是2013年藍橋杯全國軟件大賽模擬題的第4題,問題是分紅酒,方法是BFS。

題目描述

標題:分紅酒

  有4個紅酒瓶子,它們的容量分別是:9升, 7升, 4升, 2升
  開始的狀態是 [9,0,0,0],也就是說:第一個瓶子滿着,其它的都空着。
  允許把酒從一個瓶子倒入另一個瓶子,但只能把一個瓶子倒滿或把一個瓶子倒空,不能有中間狀態。這樣的一次倒酒動作稱爲1次操作。
  假設瓶子的容量和初始狀態不變,對於給定的目標狀態,至少需要多少次操作才能實現?
  本題就是要求你編程實現最小操作次數的計算。
 
  輸入:最終狀態(逗號分隔)
  輸出:最小操作次數(如無法實現,則輸出-1)

例如:
輸入:
9,0,0,0
應該輸出:
0
輸入:
6,0,0,3
應該輸出:
-1
輸入:
7,2,0,0
應該輸出:
2

對於編程題目,要求選手給出的解答完全符合ANSI C++標準,不能使用諸如繪圖、Win32API、中斷調用、硬件操作或與操作系統相關的API。
代碼中允許使用STL類庫,但不能使用MFC或ATL等非ANSI c++標準的類庫。例如,不能使用CString類型(屬於MFC類庫)。
所有代碼放在同一個源文件中,調試通過後,拷貝提交該源碼。
注意選擇自己使用的編譯環境。

分析

這是一個關於廣度優先搜索(BFS)的題目。每個杯中水的量只能爲整數,所以狀態是有限的,遍歷這些狀態,並找出距離初始狀態最短的路徑,即爲所求解。

廣度優先搜索(BFS)使用隊列實現,正如深度優先搜索(DFS)使用堆棧實現。

解題思路確定了,代碼實現就簡單一點了,當然也會出現小錯誤。

程序實現

數組模擬隊列

隊列是一種常用的先進先出(FIFO)的數據結構,常用數組模擬實現。

#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
typedef struct Node {
	int v[4];
	int dist;
} Node;
int vis[10][10][10];
int count = 0;
int a, b, c, d;
void bfs(void) {
	Node q[100000];
	int front = 0;
	int rear = 1;
	Node state;
	int cap[4] = {9, 7, 4, 2};
	state.v[0] = 9;
	state.v[1] = state.v[2] = state.v[3] = 0;
	state.dist = 0;
	q[front] = state;
	vis[state.v[1]][state.v[2]][state.v[3]] = 1;
	while(rear > front) {
		Node olds = q[front];
		if(olds.v[0] == a && olds.v[1] == b && olds.v[2] == c && olds.v[3] == d) {
			cout << olds.dist << endl;
			return;
		}
		for(int i = 0; i < 4; i++) {
			for(int j = 0; j < 4; j++) {
				if(i == j) {
					continue;
				}
				// i to j
				Node & news = q[rear];
				memcpy(&news, &olds, sizeof(Node));
				int amount = min(olds.v[i], cap[j] - olds.v[j]);
				news.v[i] -= amount;
				news.v[j] += amount;
				news.dist++;
				if(!vis[news.v[1]][news.v[2]][news.v[3]]) {
					vis[news.v[1]][news.v[2]][news.v[3]] = 1;
					rear++;
				}
			}
		}
		front++;
	}
	cout << "-1" << endl;
}
int main(void) {
	cin >> a >> b >> c >> d;
	memset(vis, 0, sizeof(vis));
	bfs();
	return 0;
}

STL隊列實現

C++ STL標準模板庫極大的提高了編程效率,雖然有時不如數組靈活,解答這個問題還是沒問題的。

#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
typedef struct Node {
	int v[4];
	int dist;
} Node;
int vis[10][10][10];
int count = 0;
int a, b, c, d;
void bfs(void) {
	queue<Node> q;
	Node state;
	int cap[4] = {9, 7, 4, 2};
	state.v[0] = 9;
	state.v[1] = state.v[2] = state.v[3] = 0;
	state.dist = 0;
	q.push(state);
	vis[state.v[1]][state.v[2]][state.v[3]] = 1;
	while(!q.empty()) {
		Node olds = q.front();
		q.pop();
		if(olds.v[0] == a && olds.v[1] == b && olds.v[2] == c && olds.v[3] == d) {
			cout << olds.dist << endl;
			return;
		}
		for(int i = 0; i < 4; i++) {
			for(int j = 0; j < 4; j++) {
				if(i == j) {
					continue;
				}
				// i to j
				Node news;
				memcpy(&news, &olds, sizeof(Node));
				int amount = min(olds.v[i], cap[j] - olds.v[j]);
				news.v[i] -= amount;
				news.v[j] += amount;
				news.dist++;
				if(!vis[news.v[1]][news.v[2]][news.v[3]]) {
					vis[news.v[1]][news.v[2]][news.v[3]] = 1;
					q.push(news);
				}
			}
		}
	}
	cout << "-1" << endl;
}
int main(void) {
	cin >> a >> b >> c >> d;
	memset(vis, 0, sizeof(vis));
	bfs();
	return 0;
}

反例分析

解決這個問題的過程也是曲折的,剛開始的時候想使用STL中以整型指針爲模板的queue,但是發現隊列中每次壓入的指針值都是相同的,把這個反例也分享一下吧。

#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
int vis[10][10][10];
int count = 0;
int a, b, c, d;
void bfs(void) {
	queue<int *> q;
	int state[5];
	int capacity[4] = {9, 7, 4, 2};
	state[0] = 9;
	state[1] = state[2] = state[3] = 0;
	state[4] = 0;
	q.push(state);
	vis[state[1]][state[2]][state[3]] = 1;
	while(!q.empty()) {
		int * tmp = q.front();
		q.pop();
		int oldState[5];
		memcpy(oldState, tmp, sizeof(oldState));
		if(oldState[0] == a && oldState[1] == b && oldState[2] == c &&  oldState[3] == d) {
			cout << oldState[4] << endl;
			return;
		}
		for(int i = 0; i < 4; i++) {
			for(int j = 0; j < 4; j++) {
				if(i == j) {
					continue;
				}
				// i to j
				int newState[5];
				memcpy(newState, oldState, sizeof(oldState));
				int quantity = min(oldState[i], capacity[j] - oldState[j]);
				newState[i] = oldState[i] - quantity;
				newState[j] = oldState[j] + quantity;
				cout << newState[0] << ", "
				     << newState[1] << ", "
					 << newState[2] << ", "
					 << newState[3] << ", "
					 << newState[4] << endl;
				if(!vis[newState[1]][newState[2]][newState[3]]) {
					vis[newState[1]][newState[2]][newState[3]] = 1;
					newState[4]++;
					cout << newState << endl;
					q.push(newState);
				}
			}
		}
	}
	cout << "-1" << endl;
}
int main(void) {
	cin >> a >> b >> c >> d;
	memset(vis, 0, sizeof(vis));
	bfs();
	return 0;
}

這個反例,在我的電腦上每次壓入隊列的值均爲0x28fe50,這是由於變量有效期的問題。

小結

雖然文中提供了多種解決方案,但核心思想都是廣度優先搜索(BFS)。

當然這個問題也可以擴展一下,把每個步驟的狀態打印出來。

(全文完)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章