题目描述
奶牛正在试验秘密代码,并设计了一种方法来创建一个无限长的字符串作为其代码的一部分使用。
给定一个字符串,让后面的字符旋转一次(每一次正确的旋转,最后一个字符都会成为新的第一个字符)。也就是说,给定一个初始字符串,之后的每一步都会增加当前字符串的长度。
输入
第一行输入一个字符串。该字符串包含最多30个大写字母,并N≤ 1 0 18 10^{18} 1018。
第二行输入N。请注意,数据可能很大,放进一个标准的32位整数可能不够,所以你可能要使用一个64位的整数类型(例如,在C / C++ 中是 long long)。
输出
给定初始字符串和索引,请帮助奶牛计算无限字符串中位置N的字符。
示例输入 COW 8
示例输出 C
这题如果直接模拟,虽然时间复杂度只有
O
(
l
o
g
n
)
O(logn)
O(logn),但空间复杂度却达到了
O
(
n
)
O(n)
O(n),因而只能得40分。
考虑将问题规模缩小,设cur_len为存在该位置且长度最小的字符串的长度。不难发现对于正好在cur_len/2+1位置上的字符,由题意它与上一个字符串的最后一个字符相同。除此之外位置n的字符与位置n-(cur_len/2+1)的字符是一样的。
因此,我们先求出cur_len,再由以上分析递归求解即可,可以写出以下代码:
#include <iostream>
#include <algorithm>
#include <string>
#define long long long
using namespace std;
string s;
int len;
int solve(long x) {
if(x<=len) return x;
int i;
for(i=1; len*(1ll<<i)<x; i++); //注意必须加加ll否则表达式len*(1<<i)可能会溢出
long cur_len=len*(1ll<<i);
if(x==cur_len/2+1) return solve(x-1);
return solve(x-(cur_len/2+1));
}
int main(){
long n;
cin>>s;
cin>>n;
len=s.length();
cout<<s[solve(n)-1];
return 0;
}
虽然这种方法已经够快,但还可以优化:
1.用二分法求解cur_len;
2.当x=cur_len/2+1时,直接将x-1 减小递归层数。注意x=len+1的特殊情况的处理。
修改后的solve函数如下:
int solve(long x) {
if(x<=len) return x;
int i=qlog2(x); //二分求解cur_len,实现在此不表,注意乘len
long cur_len=len*(1ll<<i);
if(x==cur_len/2+1) {
x--;
cur_len/=2;
if(x<=len) return x;
}
return solve(x-(cur_len/2+1));
}