最近在ecnu的ACM上看到一道題:http://acm.cs.ecnu.edu.cn/problem.php?problemid=1802是數據結構中關於火車調度的算法(已經忘了這道題了。。)。剛開始沒有在網上查資料,自己在那瞎琢磨,想着通過檢驗字符的相互順序來判斷一個情況是否能出現,但總是感覺不對頭,應該還有簡單的方法。後來在網上查了資料,發現是用兩個棧來模擬火車的進出。爲了使講述方便,新增一個棧,就是火車出站時使用的棧。這樣就有三個棧:火車進站前進入棧right,在站中時,是在棧middle,出棧時進入棧left。
比如對於輸入“4 4321”,那麼right將初始化爲1234(從左往右看),檢查input的第一個元素是4,因爲middle跟right的top都不是元素4,那麼火車1進站,此時middle中是1,right中是234,如上比較,直到middle中是321(從上往下看),right中是4,這時發現right中有元素4,那麼right棧彈出,進入棧left(此題可以不使用left),input的指針前移,檢測到元素3,因爲在middle中有,所以3出站,這樣直到檢查完input,發現沒有問題,輸出“yes”。
分析失敗的情況:“4 4312”,right爲1234,middle爲空,left爲空,4、3的檢測跳過,這時middle爲2,1,right爲空,input指針指向1,但是middle的top不是1,所以只能從right中進站,但是right爲空,這時發生衝突,1的位置是不對的。
根據以上的分析給出代碼:
#include <iostream>
#include <string>
#include <stack>
using namespace std;
void problem_1802()
{
typedef string::size_type size_type;
int N; cin >> N;
stack<char> right;
stack<char> middle;
for (int n = 0; n < N; n++)
{//
while (!right.empty())
{//
right.pop();
}
char index; cin >> index;
for (char c = index; c > '0'; c--)
{//
right.push(c);
}
//因爲火車的序號是一位數,而且input中的元素是連續的,所以使用string存儲input
string str; cin >> str;
string::size_type i;
bool failed; //判斷標誌,使用goto就沒這麼麻煩了。
for (i = 0; i != str.size(); i++)
{//
failed = false;
char c = str[i];
//從middle和right中查找棧頂元素
//如果找到則放入到left棧中
if (!right.empty() && c == right.top())
{//從right棧中找
right.pop();
}
else if (!middle.empty() && c == middle.top())
{//從middle棧中找
middle.pop();
}
else
{//否則兩個棧的棧頂都不是查找元素,需要從right中將c“刨出來”
failed = true;
while (!right.empty())
{//
char cc = right.top();
if (cc == c)
{//放入到left中
failed = false;
right.pop();
break;
}
//如果cc != c,那麼將cc挖出來,放入到middle中,使c能重見天日。
middle.push(cc);
right.pop();
}
}
if (failed)
{//
break;
}
}
if (i == str.size())
{//
cout << "yes" << endl;
}
else
{
cout << "no" << endl;
}
}
}
但是提交的時候是TImeLimited Fail,覺得可能是因爲每次都對input進行檢查太浪費時間了,應該先把index爲1-9的列車所有的排列情況都算出來,存入一個set中查找。所以問題變成列舉出列車所有的出站情況。
當從right出棧時,一輛火車有兩種選擇:先進站,然後出站,或者先進站,但是不出站;這對應兩種操作,從right中直接pop,或者push進middle中。
那麼遞歸的操作應該是:
left.push(pushed);
f(left, middle, pushed+1);
left.pop();
middle.push(pushed);
f(left, middle, pushed+1);
middle.pop();
可見遞歸結束的條件是當pushed爲'9'時停止,這時right中已經沒有元素了,但是left和middle中有,比如left中的元素可能是1234,middle中的元素爲8765,這時需要模擬火車的進出站順序了:
首先9可以直接進入left,然後middle中的所有元素出棧並壓入left,right爲123498765
但是8也可能先於9進入left,然後9進入left,middle中的所有元素765進入left123489765
........
可見是將9在middle的棧中依次“插隊”,每種插隊對應一種情況。下面是代碼
typedef stack<char> container_type;
set<container_type> conds;
int fail_counter = 0; //用來檢測重複情況
//計算9輛火車的情況
void f(container_type& left, container_type& middle, const char pushed)
{
if (pushed == '9')
{//處理right棧中的最後一列火車
//爲了保持left和middle的不變,使用tmp_進行操作
stack<char> tmp_left = left;
stack<char> tmp_middle = middle;
for (size_t i = 0; i <= middle.size(); i++)
{//將'9'在middle的棧中依次“插隊”
//將middle中,pushed前的元素放入到left中。
for (size_t j = 0; j < i; j++)
{//j是pushed在middle的隊列中的位置
tmp_left.push(tmp_middle.top());
tmp_middle.pop();
}
//然後將pushed放入到left中
tmp_left.push(pushed);
while (!tmp_middle.empty())
{//最後將middle中,pushed後的放入到left中
tmp_left.push(tmp_middle.top());
tmp_middle.pop();
}
//注意順序,我們需要給出像input中那樣的順序,所以把棧中的元素顛倒放入到conds中
while (!tmp_left.empty())
{//
tmp_middle.push(tmp_left.top());
tmp_left.pop();
}
if (!(conds.insert(tmp_middle).second))
{//檢測是否重複
//while (!tmp_middle.empty())
//{//
// cout << tmp_middle.top() << ", ";
// tmp_middle.pop();
//}
//cout << endl;
fail_counter++;
}
//重置
tmp_left = left;
tmp_middle = middle;
}
}
left.push(pushed);
f(left, middle, pushed+1);
left.pop();
middle.push(pushed);
f(left, middle, pushed+1);
middle.pop();
}
但是得到了9的出車情況,還有8的情況,7的情況……,好辦,使用一個for循環就夠了,將遞歸退出的if語句依次改爲12345678就好了。
但是仔細研究發現,在得到9的出車情況時,其實已經計算了其它車的出車情況,比如,在puehed爲5的情況中,left和middle中的所有元素都是不超過5的,也就是它們中是1234的各種合理組合,所以我們可以在計算9時計算12345678的合理情況:
typedef stack<char> container_type;
set<container_type> conds_set[10];
int fail_counter = 0;
void f(container_type& left, container_type& middle, const char pushed, const char end_chr)
{
set<container_type>& conds = conds_set[pushed-'0'];
stack<char> tmp_left = left;
stack<char> tmp_middle = middle;
for (size_t i = 0; i <= middle.size(); i++)
{//
//將middle中,pushed前的元素放入到left中。
for (size_t j = 0; j < i; j++)
{//j是pushed在middle的隊列中的位置
tmp_left.push(tmp_middle.top());
tmp_middle.pop();
}
//然後將pushed放入到left中
tmp_left.push(pushed);
//最後將middle中,pushed後的放入到left中
while (!tmp_middle.empty())
{//
tmp_left.push(tmp_middle.top());
tmp_middle.pop();
}
//注意順序,我們需要給出像input中那樣的順序,所以把棧中的元素顛倒放入到conds中
while (!tmp_left.empty())
{//
tmp_middle.push(tmp_left.top());
tmp_left.pop();
}
if (!(conds.insert(tmp_middle).second))
{//檢測是否重複
//while (!tmp_middle.empty())
//{//
// cout << tmp_middle.top() << ", ";
// tmp_middle.pop();
//}
//cout << endl;
fail_counter++;
}
tmp_left = left;
tmp_middle = middle;
}
if (pushed == end_chr)
{//
return;
}
left.push(pushed);
f(left, middle, pushed+1, end_chr);
left.pop();
middle.push(pushed);
f(left, middle, pushed+1, end_chr);
middle.pop();
}
數據結構的情況是,存儲一種合理情況的是stack<char>類型,給它起了別名叫做container_type,然後一個index對應多個不同的合理情況,爲了去除相同的情況使用set數據結構,index從1-9,那麼使用一個數組來代表9個index的組合。
但是每次Submit的時候都重新計算太浪費時間,所以應該把結果放入到一個數組中,輸入一個index和它對應的情況就查這個數組看看有沒有,這樣就只有查找,沒有重新計算了:
#include <fstream>
void to_file()
{
fstream fs;
fs.open("C:/1.txt", ios_base::out | ios::trunc);
if (!fs)
{//
cout << "can't open!" << endl;
}
container_type left;
container_type middle;
f(left, middle, '1', '9');
for (size_t i = 1; i < 10; i++)
{//
fs << "{" << endl;
set<container_type> conds = conds_set[i];
for (set<container_type>::iterator itr = conds.begin(); itr != conds.end(); ++itr)
{//
fs << "\t{";
container_type& c = *itr;
while (!c.empty())
{//
fs << c.top();
if (c.size() != 1)
{//
fs << ", ";
}
c.pop();
}
set<container_type>::iterator itr2 = itr;
itr2++;
fs << "}";
if (itr2 != conds.end())
{//
fs << ", ";
}
fs << endl;
}
fs << "}, " << endl;
}
fs.close();
}
走了。