1. 题目
Now there is a stack with unlimited capacity. You are supposed to push numbers in a given sequence into the stack one by one, and can pop randomly.
So your task is to check whether a given sequence of numbers is a possible pop sequence. For example, if
2 1 3 6 1 2
is the push sequence,3 6 1 1 2 2
is a valid pop sequence while3 6 2 2 1 1
is not.Input Specification:
Each input file contains one test case. For each test case, the first line contains an integer N (no larger than 20) representing the length of the push sequence, whose N elements are given in the second line. Then the third line contains a number K (no larger than 10) denoting the number of pop sequences to be checked. Then K lines follow, each contains a pop sequence consisting of N numbers. All the numbers in a line are separated by a space.
Output Specification:
For each pop sequence to be checked, print in one line
Yes
if it is valid orNo
if not.Sample Input:
6 2 1 3 6 1 2 2 3 6 1 1 2 2 3 6 2 2 1 1
Sample Output:
Yes No
代码长度限制
16 KB
时间限制
200 ms
内存限制
64 MB
这道题总之就是判断出栈的合法序列。
入栈序列:2 1 3 6 1 2
序列编号:1 2 3 4 5 6
按照这个编号入栈,然后看它询问的出栈序列是否合法。
2. 方法分析
首先,直接模拟栈的方法是不行的。
6
2 1 3 6 1 2
(入栈编号:1 2 3 4 5 6)
1
2 3 1 2 6 1
(出栈编号:1 3 5 6 4 2 )
这个例子是正确的,但是用模拟栈是会出错的。
auto check = [&]()
{
// 合法出栈序列判断
stack<int> s;
int i = 0, j = 0;
// now 数组表示出栈序列
for (i, j; i < n; i++)
{
s.push(i + 1); // i + 1 就是入栈编号,默认从 1~n 的顺序
while (j < n && !s.empty() && s.top() == now[j]) s.pop(), j++;
}
return s.empty();
};
原因就是入栈序列存在重复的数字。
对于重复的数字,它的入栈编号是无法确定的。
这样就有一种方法:
先随意的将出栈序列用入栈的编号代换,然后对于重复的数字,尝试交换它们的编号,然后用模拟栈的方法做判断,只要遇到一个正确的就可以了。
// tmp 是 multimap,用于存储入栈的数字与其对应的编号
// tap 是入栈序列
// *itr.first 是入栈的数字,*itr.second 是编号
for (int i = 0; i < n; i++)
{
auto itr = tmp.find(tap[i]);
if (itr != tmp.end())
{
now.push_back((*itr).second);
tmp.erase(itr);
}
else checkk = 1; // 出栈序列存在入栈序列没出现过的数
}
最后一个问题是交换数字的方法。
首先可以看到,这个栈序列的总数不超过20,询问次数也不超过10次。
可以使用一个二重循环去交换数字:
for (int i = 0; i < n; i++)
{
for (int j = i; j < n; j++)
{
if (tap[i] == tap[j]) // 对重复的数字交换编号
{
swap(now[i], now[j]);
res = check();
if (res) break; // 发现这个出栈序列是合法的
}
}
if (res) break;
}
但是这个循环只是跑了这个序列一次,没有覆盖所有的交换可能,因此在外面再套个循环,至于这个循环的次数,我是估计出来的,比较玄学。
比如,按照上面的方法,假设入栈序列有相同的数字,但是它们的编号不同,为1,2,3.
开始:1 2 3
第一次:2 1 3
第二次:2 3 1
双重循环结束
第三次:3 2 1
第四次:3 1 2
双重循环结束
第五次:1 3 2
第六次:1 2 3
双重循环结束
三重循环结束
一共六次。
这个我感觉和组合数有点关系。
尝试考虑一下20个的情况下存在19个重复的数,这个情况随便交换数字都可以直接获得正确的序列,因此我估计第三重的循环大概是n^3左右。
最后一点,上面我讨论的重复数字交换的可能结果是对于一种重复数字,那对于多种重复数字,在交换数字里多加一个特判:
if (tap[i] == tap[j])
{
swap(now[i], now[j]);
res = check();
if (k & 1) swap(now[i], now[j]); // 根据循环,一次交换,一次交换后返回原位
if (res) break;
}
尽量覆盖更多的可能性。
总之就是玄学的方法。
最后的AC代码:
#include<bits/stdc++.h>
using namespace std;
signed main()
{
int n;
cin >> n;
vector<int> v(n);
multimap<int, int> mp;
for (int i = 0; i < n; i++)
{
cin >> v[i];
mp.insert({v[i], i + 1});
}
int q;
cin >> q;
while (q--)
{
vector<int> tap(n);
multimap<int, int> tmp(mp);
for (auto &i: tap) cin >> i;
vector<int> now;
int checkk = 0;
for (int i = 0; i < n; i++)
{
auto itr = tmp.find(tap[i]);
if (itr != tmp.end())
{
now.push_back((*itr).second);
tmp.erase(itr);
}
else checkk = 1;
}
if (checkk)
{
cout << "No" << endl;
continue;
}
auto check = [&]()
{
stack<int> s;
int i = 0, j = 0;
for (i, j; i < n; i++)
{
s.push(i + 1);
while (j < n && !s.empty() && s.top() == now[j]) s.pop(), j++;
}
return s.empty();
};
int res = check();
if (res) cout << "Yes" << endl;
else
{
int cnt = n * n * n;
for (int k = 0; k < cnt; k++)
{
for (int i = 0; i < n; i++)
{
for (int j = i; j < n; j++)
{
if (tap[i] == tap[j])
{
swap(now[i], now[j]);
res = check();
if (k & 1) swap(now[i], now[j]);
if (res) break;
}
}
if (res) break;
}
if (res) break;
}
if (res) cout << "Yes" << endl;
else cout << "No" << endl;
}
}
return 0;
}