题188.洛谷P1563 模拟-[NOIP2016 提高组] 玩具谜题


题188.洛谷P1563 模拟-[NOIP2016 提高组] 玩具谜题


一、题目

在这里插入图片描述
在这里插入图片描述

二、题解

本题要你得到从开始的小人开始执行指令到最后位于哪一个小人,过程中包含了要针对小人不同的朝向–向内向外以及变更小人时的指令方向–向左向右。因为变动过程中会出现"绕圈现象",所以不难想到可以用队列来模拟过程。 我们把每个小人按照输入顺序作为编号存入队列中。
1.当小人朝内,指令向左时,每动一次就将队尾元素拿到队头,指令向右时,每动一次就将队头元素拿到队尾。做完指令后的队头元素即为变动到的小人编号。拿样例1的第一条指令为例,0编号小人朝内,指令向左(往左手方向),做"队尾甩队头",三次移动变动后到编号4:
在这里插入图片描述
2.当小人朝外时,过程正好和朝内时相反,指令向左时做"队头甩队尾",指令向右时做"队尾甩队头"。同样队头取结果
综上,队列应选用双端队列deque合适。虽然这个模拟过程十分直接,但由于m,n可能很大,导致了部分测试点超时,所以这个代码只能拿80分。代码如下:

#include <bits/stdc++.h>

using namespace std;

struct People
{
    string name;
    int direction;
}P[100001];//下标为那个小人的编号

int main()
{
    int n,m;
    cin>>n>>m;
    deque<int> dq;
    for(int i=1;i<=n;i++)
    {
        cin>>P[i].direction>>P[i].name;
        dq.push_back(i);//初始化队列,队尾插入元素
    }
    for(int i=0;i<m;i++)
    {
        int way,num;
        cin>>way>>num;
        int start=dq.front();//队头拿到初始元素
        while(num)
        {
            if(P[start].direction==0)//小人方向朝内
            {
                if(way==0)//指令向左
                {
                    int temp=dq.back();//取队尾元素
                    dq.pop_back();//删除队尾元素
                    dq.push_front(temp);//队头插入
                }
                else//指令向右
                {
                    int temp=dq.front();//取队头元素
                    dq.pop_front();//删除队头元素
                    dq.push_back(temp);//队尾插入
                }
            }
            else
            {
                if(way==1)
                {
                    int temp=dq.back();
                    dq.pop_back();
                    dq.push_front(temp);
                }
                else
                {
                    int temp=dq.front();
                    dq.pop_front();
                    dq.push_back(temp);
                }
            }
            num--;
        }
    }
    cout<<P[dq.front()].name<<endl;//始终是队头为结果,取结果输出
}

拓:关于deque的用法

下面介绍满分代码思路。因为队列模拟是变动时一个一个真的去变,循环用了两层,效率比较低,现想法子去掉一层循环。因为每次变动是往一个方向变,只是变动的步数不一样,所以我们不妨直接一变变到底,但是这样我们就必须舍去直接用队列模拟了。模仿队列模拟的思路,其实我们要做到"绕圈现象",就是增大编号时超最大编号范围了做一波用小人总数求余就好了,但是这样不是只能往一个方向变了么?毕竟减小编号时超了范围做求余的话就直接整出个负数编号出来了。所以为了能拓展减小编号的方向,所以我们再整一个逆着输入顺序的编号,这样我们可以直接做增大编号等价于正着编号时做减小编号。这样我们对发出两个方向的指令就都可以模拟到了。代码如下:

#include <bits/stdc++.h>

using namespace std;

struct People
{
    string name;
    int direction;
} P[100000],ReP[100000];//依旧是下标表示name对应小人的编号

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=0; i<n; i++)//必须从0开始放,不然你求余得位置要出事
    {
        cin>>P[i].direction>>P[i].name;
        ReP[n-i-1].direction=P[i].direction;
        ReP[n-i-1].name=P[i].name;
    }
    //start:当前所处的位置,这个位置可能时正向编号表里的位置,也可能是逆向编号表里的位置
    //flag:表示当前所处的状态,1表示使用正向编号,0表示使用逆向编号
    int start=0,flag=1;
    for(int i=0; i<m; i++)
    {
        int way,num;
        int direction;
        cin>>way>>num;
        if(flag==1)//记得每次都看下当前是处于正向编号表还是逆向编号表
        {
            direction=P[start].direction;
        }
        else
        {
            direction=ReP[start].direction;
        }
        if(direction==0)//小人方向朝内
        {
            if(way==0)//指令向左,做"队尾甩队头",使用逆向编号
            {
                if(flag==1)//如果此前是用的正向编号则要将start更改到逆向编号时那个name对应的小人的位置
                {
                    start=n-start-1;
                    flag=0;//同时变更flag
                }
                start=(start+num)%n;//做增大编号求余操作
            }
            else//指令向右,做"队头甩队尾",使用正向编号
            {
                if(flag==0)//同理
                {
                    start=n-start-1;
                    flag=1;
                }
                start=(start+num)%n;
            }
        }
        else//朝外,过程相反,同理
        {
            if(way==1)
            {
                if(flag==1)
                {
                    start=n-start-1;
                    flag=0;
                }
                start=(start+num)%n;
            }
            else
            {
                if(flag==0)
                {
                    start=n-start-1;
                    flag=1;
                }
                start=(start+num)%n;
            }
        }
    }
    if(flag==1)//看是用正向编号位置的小人输出还是逆向编号位置的小人输出
    {
        cout<<P[start].name<<endl;
    }
    else
    {
        cout<<ReP[start].name<<endl;
    }
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值