洛谷P3612 [USACO17JAN] Secret Cow Code S

发布时间 2023-09-25 18:31:18作者: 凪风sama

[USACO17JAN] Secret Cow Code S

题面翻译

奶牛正在试验秘密代码,并设计了一种方法来创建一个无限长的字符串作为其代码的一部分使用。

给定一个字符串,让后面的字符旋转一次(每一次正确的旋转,最后一个字符都会成为新的第一个字符)。也就是说,给定一个初始字符串,之后的每一步都会增加当前字符串的长度。

给定初始字符串和索引,请帮助奶牛计算无限字符串中位置 \(N\) 的字符。

第一行输入一个字符串。该字符串包含最多 \(30\) 个大写字母,数据保证 \(N \leq 10^{18}\)

第二行输入 \(N\)。请注意,数据可能很大,放进一个标准的 \(32\) 位整数可能不够,所以你可能要使用一个 \(64\) 位的整数类型(例如,在 C/C++ 中是 long long)。

请输出从初始字符串生成的无限字符串中的位置的字符。第一个字符是 \(N=1\).。

样例 #1

样例输入 #1

COW 8

样例输出 #1

C

提示

In this example, the initial string COW expands as follows:

COW -> COWWCO -> COWWCOOCOWWC

以上为题目。
这道题题目描述很简单,就是将一个字符串的最后一个字符当作第一个字符,然后将剩下的字符复制下来。

有一个很朴素的想法,那就是模拟题目中所描述的字符串旋转的过程,创造一个长度大于N的字符串,最后直接输出Arr[N-1]即可,代码如下

#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
long long N = 0;
string S;
void Solve(string& s)
{
	if (s.size() >= N)
	{
		return;
	}	
	int len = s.size() - 1;
	s.push_back(s[s.size() - 1]);
	for (int i = 0; i < len; i++)
	{
		s.push_back(s[i]);
	}
	Solve(s);
}
int main()
{
	cin >> S >> N;
	Solve(S);
	cout << S[N-1];
	return 0;
}

可想而知,如果N足够大,字符串的长度也会惊人的大,最后导致MLE。

因此,我们要想一种方法,不用真正的模拟出旋转后的字符串,而是仅通过递推来找出目标字符在未旋转字符串中的位置

不如假设经过多次旋转操作后的字符串长度为2*N

根据题目描述,则此字符串的排列有以下特点(这里我们用数字来代表单个字符)

\(1, 2, 3, 4, 5, ...,N,N+1,N+2,...,2N\)

其中,N即为旋转前字符串的最后一个字符,而N+1即为旋转后新增字符串的第一个字符,简而言之,就是
\(Str[N]==Str[N+1]\)

根据旋转的规则,显然我们有N左边的字符串和N+1右边的字符串对应相等,即\(Str[1]==Str[N+2]\),以此类推。

而且,题设中所寻找的无限字符串第K位置上的数,总是在N+1 ~ 2N范围之内,所以我们可以不断的二分缩小查找区间,让N+1 ~ 2N内的问题变为1 ~ N内的问题。

也就是对于题目 N+2<=K<=2*N, Str[K] = Str[K-N-1],而对于K==N+1的情况,要进行特判

以下为代码

#include<iostream>
#include<string>
using namespace std;
string S;
long long k, t;
void Solve(long long K)
{
//递归推出条件即为n收敛到子字符串内,便输出推出递归
	if (n <= S.size())
	{
		cout << S[n - 1];
		return;
	}
	long long N = S.size();
	while ((N << 1) < K)N <<= 1;
	//这里使用左移位替代乘2
	//通过不断乘以二来试探出解题所需要的最小N(即满足使得N+1<= K<=2*N的N)
	K = K - N - 1;//将问题区间缩小至1~N
	if (K == 0)K = N;//对于K==N+1情况的特判
	Solve(K);//继续递归缩小区间

}
int main()
{
	cin >> S >> k;//数据输入
	Solve(k);
	return 0;
}