最近在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
#include
#include
using namespace std;
void problem_1802()
{
typedef string::size_type size_type;
int N; cin >> N;
stack right;
stack 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 container_type;
set 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 tmp_left = left;
stack 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 container_type;
set conds_set[10];
int fail_counter = 0;
void f(container_type& left, container_type& middle, const char pushed, const char end_chr)
{
set& conds = conds_set[pushed-'0'];
stack tmp_left = left;
stack 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
但是每次Submit的时候都重新计算太浪费时间,所以应该把结果放入到一个数组中,输入一个index和它对应的情况就查这个数组看看有没有,这样就只有查找,没有重新计算了:
#include
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 conds = conds_set[i];
for (set::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::iterator itr2 = itr;
itr2++;
fs << "}";
if (itr2 != conds.end())
{//
fs << ", ";
}
fs << endl;
}
fs << "}, " << endl;
}
fs.close();
}
走了。