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