题目背景
NOIP2016 提高组 D1T1
题目描述
小南有一套可爱的玩具小人, 它们各有不同的职业。
有一天, 这些玩具小人把小南的眼镜藏了起来。 小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的面朝圈外。如下图:
这时 singer 告诉小南一个谜題: “眼镜藏在我左数第 33 个玩具小人的右数第 11 个玩具小人的左数第 22 个玩具小人那里。 ”
小南发现, 这个谜题中玩具小人的朝向非常关键, 因为朝内和朝外的玩具小人的左右方向是相反的: 面朝圈内的玩具小人, 它的左边是顺时针方向, 右边是逆时针方向; 而面向圈外的玩具小人, 它的左边是逆时针方向, 右边是顺时针方向。
小南一边艰难地辨认着玩具小人, 一边数着:
singer 朝内, 左数第 33 个是 archer。
archer 朝外,右数第 11 个是 thinker 。
thinker 朝外, 左数第 22 个是 writer。
所以眼镜藏在 writer 这里!
虽然成功找回了眼镜, 但小南并没有放心。 如果下次有更多的玩具小人藏他的眼镜, 或是谜题的长度更长, 他可能就无法找到眼镜了。所以小南希望你写程序帮他解决类似的谜题。 这样的谜題具体可以描述为:
有 nn 个玩具小人围成一圈, 已知它们的职业和朝向。现在第 11 个玩具小人告诉小南一个包含 mm 条指令的谜題, 其中第 zz 条指令形如“左数/右数第 ss,个玩具小人”。 你需要输出依次数完这些指令后,到达的玩具小人的职业。
输入格式
输入的第一行包含两个正整数 n,mn,m,表示玩具小人的个数和指令的条数。
接下来 nn 行,每行包含一个整数和一个字符串,以逆时针为顺序给出每个玩具小人的朝向和职业。其中 00 表示朝向圈内,11 表示朝向圈外。 保证不会出现其他的数。字符串长度不超过 1010 且仅由小写字母构成,字符串不为空,并且字符串两两不同。整数和字符串之间用一个空格隔开。
接下来 mm 行,其中第 ii 行包含两个整数 a_i,s_iai,si,表示第 ii 条指令。若 a_i=0ai=0,表示向左数 s_isi 个人;若 a_i=1ai=1,表示向右数 s_isi 个人。 保证 a_iai 不会出现其他的数,1 \le s_i < n1≤si<n。
输出格式
输出一个字符串,表示从第一个读入的小人开始,依次数完 mm 条指令后到达的小人的职业。
输入输出样例
输入 #1复制
7 3 0 singer 0 reader 0 mengbier 1 thinker 1 archer 0 writer 1 mogician 0 3 1 1 0 2
输出 #1复制
writer
输入 #2复制
10 10 1 C 0 r 0 P 1 d 1 e 1 m 1 t 1 y 1 u 0 V 1 7 1 1 1 4 0 5 0 3 0 1 1 6 1 2 0 8 0 4
输出 #2复制
y
说明/提示
【样例1说明】
这组数据就是【题目描述】 中提到的例子。
【子任务】
子任务会给出部分测试数据的特点。 如果你在解决题目中遇到了困难, 可以尝试只解决一部分测试数据。
每个测试点的数据规模及特点如下表:
其中一些简写的列意义如下:
-
全朝内: 若为“√”, 表示该测试点保证所有的玩具小人都朝向圈内;
-
全左数:若为“√”,表示该测试点保证所有的指令都向左数,即对任意的 1\leq z\leq m, a_i=01≤z≤m,ai=0;
-
s=1s=1:若为“√”,表示该测试点保证所有的指令都只数 11 个,即对任意的 1\leq z\leq m,s_i=11≤z≤m,si=1;
职业长度为 11:若为“√”,表示该测试点保证所有玩具小人的职业一定是一个长度为11的字符串。
l两种解法
1 .一、先来往右边数
那么T的右边一个数的下标就是T+1,T的右边n个数的下标就是T+n啦!
我在访问队列的时候,要循环n次 每次T+=1 然后判断如果T>=len_a 则T-= len_a;
后来发现懒得写if,索性改成:
循环n次 每次T+=1 然后T%=len_a;//这么写,T始终是在[0,len_a-1]区间内
后来发现最快的:
然后使用公式right(T)=(T+n)%len_a;
直接就找到了T右边第n个数的下标。
二、 然后往左边数了
同样的,那么第T个数的左边n个数的下标是:T-n
但是这样可能为负数。之前说过了,最多往左边数len_a-1人,再多数一个就回到原点。
则:T-(n%len_a) 有可能为负数 但是T-(n%len_a)+len_a绝对非负因为 (n%len_a) < len_a ,然后再mod上一个len_a防止超出即可。但是这样的话当 T = len_a 且 n=0 时 会出错
所以改成事先判断n是否为0就不会。
公式: left(T)=(T-(n%len_a)+len_a)%len_a
三、判断向哪一边数人
这个很简单。观察可知当代表方向的bool值与代表小人朝向的bool值不相等,则为顺时针(往左边),相等则为逆时针(往右边)
**这样的话只需要对下标T进行操作,赋值语句轻松解决。
附上代码,很短: **
#include<iostream>
#include<vector>
using namespace std;
int n,m;
vector<string> name;
vector<bool> w;
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
bool a;string b;
cin>>a>>b;//读人
w.push_back(a);
name.push_back(b);
}
int point=0;//下标从0开始数
while(m--)//循环m次的简写
{
bool f;
int num;
cin>>f>>num;//读人指令
if(num!=0)
{
if(w[point]^f)//异或符^,不等时为1否则为0。
point=(point+num)%n;//这里如果f和W不等,则顺时针
else
point=(point-num%n+n)%n;//否则逆时针
}
}
cout<<name[point];//输出
return 0;
}
2.
这怕不是这题唯一一篇O(nlogn)O(nlogn)的题解……
我们发现这要让我们每次不断地往某个方向跳某些步,我们希望加速这一过程,然后我们很自然的就能想到倍增,毕竟这是套路嘛。就是我们设dp[0/1][i][j]dp[0/1][i][j]为(我们在这里规定0是顺时针,1是逆时针)我们从ii出发,向顺/逆时针跳了2^j2j步,到达了什么地方,显然他要跳2^j2j步当j>0j>0的时候需要先从ii开始跳2^{j-1}2j−1步,然后从跳到的这个地方开始接着跳2^{j-1}2j−1步就达到了从ii开始跳2^j2j步的效果,即
dp[0/1][i][j]=dp[0/1][dp[0/1][i][j-1]][j-1]dp[0/1][i][j]=dp[0/1][dp[0/1][i][j−1]][j−1]。
注意边界条件就是j=0j=0的时候为他跳一步到的位置。
然后我们每读入一个ss就将他二进制拆分成\sum 2^{a_i}∑2ai的形式,显然拆分的数量是lognlogn级别的,每个2的幂都可以用倍增来跳,毕竟跳的过程是一个累积的过程。
别问我为啥不写模拟,作为一个再过一天就noip的蒟蒻重新看到这题的第一想法居然是倍增……
上代码~
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
namespace ywy
{
inline int get()//我的垃圾快读
{
int n=0;
char c;
while((c=getchar())||23333)
{
if(c>='0'&&c<='9')break;
if(c=='-')goto s;
}
n=c-'0';
while((c=getchar())||23333)
{
if(c>='0'&&c<='9')n=n*10+c-'0';
else return(n);
}
s:
while((c=getchar())||23333)
{
if(c>='0'&&c<='9')n=n*10-c+'0';
else return(n);
}
}
inline void get(char *str)//我的垃圾字符串快读
{
char c;
register int ptr=1;
while((c=getchar())||23333)if(c>='A'&&c<='z')break;
str[ptr]=c;
ptr++;
while((c=getchar())||23333)
{
if(c>='a'&&c<='z')str[ptr]=c,ptr++;
else return;
}
} int dp[2][100011][17];//倍增数组
char str[100011][15];//字符串
unsigned char dir[100011];//每个人的朝向
int lg[100011];//我们把每个数的log2预处理出来
void ywymain() //注意!输入顺序为逆时针,0顺1逆!
{
int n=get(),m=get();
lg[0]=-1;
for(register int i=1; i<=n; i++)
{
dir[i]=get(),get(str[i]);
lg[i]=lg[i-1];
if(i==(i&-i))lg[i]++;//预处理log2
}
for(register int i=2; i<=n; i++)dp[0][i][0]=i-1;
for(register int i=1; i<n; i++)dp[1][i][0]=i+1;//边界
dp[0][1][0]=n;
dp[1][n][0]=1;//注意这是环
for(register int i=1; i<=lg[n]; i++)//递推出倍增数组
{
for(register int j=1; j<=n; j++)
{
dp[0][j][i]=dp[0][dp[0][j][i-1]][i-1];
dp[1][j][i]=dp[1][dp[1][j][i-1]][i-1];
}
}
int cur=1;//当前的位置
while(m)
{
m--;
int a=get(),s=get();
int fx=dir[cur]^a;//他的顺序根据我们的规定异或一下朝向和方向就行了
for(register int i=lg[s]; i>=0; i--)//二进制拆分
{
if(s&(1<<i))cur=dp[fx][cur][i];
}
}
cout<<(str[cur]+1)<<endl;
}
}
int main()
{
ywy::ywymain();
return(0);
}