PTA Pop Sequence - Hard Version分数 30

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 while 3 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 lineYes if it is valid or No 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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值