题目描述
The cows are experimenting with secret codes, and have devised a method for creating an infinite-length string to be used as part of one of their codes.
Given a string s, let F(s)F be s followed by s "rotated" one character to the right (in a right rotation, the last character of ss rotates around and becomes the new first character). Given an initial string s, the cows build their infinite-length code string by repeatedly applying F; each step therefore doubles the length of the current string.
Given the initial string and an index N, please help the cows compute the character at the Nth position within the infinite code string.
输入格式
The input consists of a single line containing a string followed by N. The string consists of at most 30 uppercase characters, and N≤10^18.
Note that N may be too large to fit into a standard 32-bit integer, so you may want to use a 64-bit integer type (e.g., a "long long" in C/C++).
输出格式
Please output the NNth character of the infinite code built from the initial string. The first character is N=1.
题意翻译
奶牛正在试验秘密代码,并设计了一种方法来创建一个无限长的字符串作为其代码的一部分使用。
给定一个字符串,对字符串进行一次操作(每一次正确的操作,最后一个字符都会成为新的第一个字符),然后把操作后的字符串放到操作前的字符串的后面。也就是说,给定一个初始字符串,之后的每一步都会增加当前字符串的长度。
给定初始字符串和 N,请帮助奶牛计算无限字符串中位置为 N 的字符。
第一行输入一个字符串。该字符串包含最多 30 个大写字母,数据保证 N≤10^18。
第二行输入 一个整数 N。请注意,数据可能很大,放进一个标准的 32 位整数容器可能不够,所以你可能要使用一个 64 位的整数容器(例如,在 C/C++ 中是 long long
)。
请输出从初始字符串生成的无限字符串中的下标为 N 的字符。第一个字符的下标是 N=1。
感谢 @y_z_h 的翻译
输入输出样例
输入 #1
COW 8
输出 #1
C
说明/提示
In this example, the initial string COW expands as follows:
COW -> COWWCO -> COWWCOOCOWWC
12345678
思路
铺垫
这道题的要求就是给定一个字符串,然后选中字符串最后一个字符将其提到最前面,再将这段字符串连接到没操作时的字符串后面。这道题的字符串是一个无限长的字符串,将其全部算完再使用数组直接提取第n位的字符的方法就行不通了。那么我们换种方法!
我们在由题目以及给的样例思考一下,这个字符串是由初始给的字符串COW,依次在其本身上面操作后在连接到原字符串上,那么它每次操作完的长度就会增加一倍(COW:3---->COWWCO:6--->COWWCOOCOWWC:12),所以我们要知道第n个字符是什么,我们就需要先求出第n个字符是进行了到长度几的时候,我们才能进行下面的操作。故就需要初始化 t = 初始字符串长度,建立一个while循环,判断条件是t < n,循环内t 每次都乘等于 2。
在知道第n个字符是在字符串长度达到几的时候可以求得了,那么同学们你们现在的想法是什么?还是不断进行操作,依次记录,然后到达第n个字符串所在的那次操作,最后将第n个输出吗?但是题目有说明N≤10^18,说明n的值是可能很大很大的,而要是记录到后面的话,数组的大小是无法达到这么大的,所以这是行不通的。
核心思路
既然n可能是很大,我们很难去直接找到它指的字符,但是由前面的推理可得字符串是在原来的基础上变化再增长的,那么我们是否可以不断还原字符串,将其分解成更小子问题,这样我们就可以通过解决子问题来得到n指向的字符。
我们观察例子的字符串,只要不是最初的那个字符串,它就可以分成两部分,它的前一半就是拼接前没操作的那部分,后一半就是操作的那部分,由题目所说的变化操作可知,操作仅仅是在原来字符串的基础上发生位置的变化,所以分两种情况:如果我们的第n位指向的是后一半某位置的话,n是可以变化成指向前一半的某一位置的,原因就是这句话“仅仅是在原来字符串的基础上发生位置的变化”;如果n是指向前一半的话,那n就不需要做什么变化。这样一来,后一半就已经不会再用到了,那么我们就将其缩小,即将长度除等于2。在上述的思路下,我们就可以实现不断还原操作前的字符串,对n进行变化,一直到最初的字符串。
那么n指向前一半的那一位置要怎么确定呢?
这里就分为两种情况:(1)n指的是后一半的第一位 (2)n指的是后一半的其他位。
(1)如果是第一位的话,n就直接等于t,因为首位其实是操作前的末位,t是字符串长度,同时也是末位的位置。
(2)如果是其他位的话,那么n -= t+1,直接由图来解释
因为要恢复成原来的字符串,那么首位就得移到最后一位,那么其他位置的字母位数就都向前了一位,同时还要减去原字符长度的一半,所以方程就是x + 1(非首位的字母向前移一位)+ len(原来的字符串长度对半砍) = n(当前n),解得x就是n - (t + 1)。
再以样例来推一次,像COWWCOOCOWWC,我们要的是第8位字符,COWWCOOCOWWC是由COWWCO与OCOWWC组合而成,COWWCO是没操作的那一半,OCOWWC就是操作完的那一半字符串。第8指的是C,那么还原成COWWCO后n就会变成1(8 - 6 - 1 = 1),然后再进行操作,将COWWCO也还原,因为此时n = 1,第一位是在没操作的那一半里,所以n是不用变化的。最后COWWCO还原成COW,已经到达最初状态了,那么此时直接输出第n位就是答案了。
讲了这么多,终于到了代码部分啦!!!
AC代码
#include<iostream>
#include<cstring>
#include<stdlib.h>
using namespace std;
long long l, n, t;
char s[55];
int main() {
cin >> s + 1 >> n;//cin>>s+1是从数组s的第1位输入,不从第0位输入只是为了方便后面s[n]的输出而已啦
l = t = strlen(s + 1);
while (t < n) t <<= 1;
while (t != l) {
t >>= 1;
if(n > t){
if (t + 1 == n) {
n = t;
}
else {
n -= 1 + t;
}
}
}
cout << s[n];
return 0;
}
这里解释一下,t <<= 1
是位左移运算符,它将 t
的所有位向左移动一位,相当于将 t
乘以2,所以 t >>= 1,就是 t 除以2,t <<= 1
的效率会比t*=2快,不过t *= 2
可以用于任何数值类型,而 t <<= 1
需要 t
是一个整数类型。