牛客网答题笔记--构造队列

这篇文章接上一篇,在牛客上关于一道约瑟夫环问题的解题笔记:

题目描述:

小明同学把1到n这n个数字按照一定的顺序放入了一个队列Q中。现在他对队列Q执行了如下程序:

while(!Q.empty())              //队列不空,执行循环
{
    int x=Q.front();            //取出当前队头的值x

    Q.pop();                 //弹出当前队头

    Q.push(x);               //把x放入队尾

    x = Q.front();              //取出这时候队头的值

    printf("%d\n",x);          //输出x

    Q.pop();                 //弹出这时候的队头
}

做取出队头的值操作的时候,并不弹出当前队头。
小明同学发现,这段程序恰好按顺序输出了1,2,3,…,n。现在小明想让你构造出原始的队列,你能做到吗?

输入描述:

第一行一个整数T(T ≤ 100)表示数据组数,每组数据输入一个数n(1 ≤ n ≤ 100000),输入的所有n之和不超过200000。

输出描述:

对于每组数据,输出一行,表示原始的队列。数字之间用一个空格隔开,不要在行末输出多余的空格.

输入例子:

4
1
2
3
10

输出例子:

1
2 1
2 1 3
8 1 6 2 10 3 7 4 9 5

题目分析:

出队的顺序是1 -> 2 -> 3 -> 4 -> 5 -> 6 ,1是第一个出队的。
题中的操作是:

  1. 先取出原队列队头元素 x = Q.front()
  2. 删除队头,这个队头已经被保留到x中了
  3. 将先前保留的队头放到队尾部
  4. printf新的队头,并且将这个队头删除,也就相当于kill这个队头

转换成约瑟夫问题:

我们看这个整个一次过程,就相当于从第一个元素开始,数到第二个元素,把这个第二个元素kill(移除队列)。之后再从这个被kill的元素的下一个元素(这时候已经成了新的队头),又开始数,到第二个又删除……..

那么就出来了:这是一个环内有6个数的约瑟夫环,q = 2的出环问题。

我们要求的,就是原先的队列的各个位置的值,也相当于每一步出队返回的下标,将其的值按照这个下标排列:

我们知道这时候的出队顺序是: 1  2  3  4  5  6
                         |  |  |  |  |  |
而约瑟夫环中出队的顺序是:   2  4  6  3  1  5

如上的一一对应关系是:值1在原队列的2号位置,值2在原队列的4号位置,值3在原队列的6号位置.....

得出的结果就是:5 1 4 2 6 3

而题目中给出的答案也是这样。
所以我们得出一种比较简单的解题思路:

将1到n重新按照这个规则来排列一边,得到的就是与输出结果对应的出队顺序,可以借此还原先前的队列:

AC代码:

#include<iostream>
#include<vector>
#include<queue>
using namespace std;

int main(void){
        int n,k;
        cin>>k;
        while(k--){
                cin>>n;
                queue<int> temp;
                vector<int> res;
                for(int i = 0;i < n;i++)
                {
                        temp.push(i+1);
                }
                while(!temp.empty()){
                        int x = temp.front();
                        temp.pop();
                        temp.push(x);
                        res.push_back(temp.front());
                        temp.pop();
                }
                vector<int> r(n+1);
                for(int i = 0;i < n;i++){
                        r[res[i]] = i+1;
                }
                for(int i = 1;i <= n;i++)
                    if(i != n)
                        cout<<r[i]<<" ";
                    else
                        cout<<r[i];
                cout<<endl;
        }
        return 0;
}

在讨论区还看到了比较好的方法:

#include <iostream>
#include <deque>
using namespace std;

int main()
{
    int n, k;
    cin >> k;
    while(k > 0)
    {
        deque<int> q;
        k--;
        cin >> n;
        for(int i = n; i > 0; i--)
        {
            q.push_front(i);
            int t = q.back();
            q.pop_back();
            q.push_front(t);
        }
        for(int i = 0; i < q.size(); i++)
            cout << q[i] << " ";
        cout << endl;
    }
} 

这个是将出队顺序的数反过来执行一遍和出队相反的操作,相当于入队操作。

1.int x = temp.front();                  3.q.push_front(t);      
  temp.pop();                            
  temp.push(x);                                 
                                           q.pop_back();  
2.res.push_back(temp.front());           2.int t = q.back();          

3.temp.pop();                            1.q.push_front(i);   

我来翻译一下:

1.取出头                    3.将取出的尾放到头部  
  删掉头                     
2.把取出的头放到尾部          2.找到尾(被放到尾部的头),删掉尾        
3.删掉新的头                 1.压入头 

那么这个过程就能看得懂了,这就是构造一个特殊的入队规则,相当于一种特殊的队列,和出队相反的规则。

好了,就到这里了,大家有什么更好的解法,欢迎提出,互相学习。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值