洛谷—P5194 [USACO05DEC]Scales S题解

本文介绍了洛谷P5194题——Scales S的解题方法。通过分析题设中砝码质量的限制,得出n≤30,进而采用搜索策略,特别是折半搜索技术。具体做法是将砝码分为两组,对每组可能测量的重量进行计算,并通过枚举与二分查找找到最大组合,以达到目标重量。最终选取所有可能答案中的最小值作为结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接: P5194 [USACO05DEC]Scales S
解题思路: 虽然题目中说n≤1000 ,但考虑到“每个砝码的质量至少等于前面两个砝码的质量的和”这一条件,可以推出 n≤30 。所以可以用搜索。可以考虑折半搜索。把40个砝码分成两半,搜索出两边分别能测量的重量,然后枚举其中一边的所有可以测量到的重量,将另外一边排序后二分,使得相加不超过C且尽量大。在所有答案中取min即可。
代码:

#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 5000000;
bool vis[maxn];
long long n,n1, c, a[maxn],sum[maxn],maxweight,cnt1,cnt2,xa[maxn],xb[maxn];
inline void DFS1(int L, int</
<think>好的,我现在需要解决洛谷的P3612题目,题目名称是“Secret Cow Code”。首先,我得仔细看看题目要求。题目大意是说,给定一个字符串S和一个整数N,当字符串以特定的方式无限扩展时,找出第N个字符。扩展的规则是每次将当前字符串的最后一个字符移到最前面,然后将这个新字符串追加到原字符串后面。比如,原字符串是“COW”,第一次扩展变成“COW” + “WCO”,第二次扩展就是“COWWCO” + “OWC” + ... 这样不断进行下去。 接下来,我需要想如何高效地找到第N个字符,因为直接模拟扩展过程显然不可行,尤其是当N很大的时候,比如题目里提到的N可以是10^18,这样的数据规模必须用数学方法或者递归的思路来解决。 首先,理解扩展的规律。每次扩展后的字符串长度是原来的两倍减一,比如原长度是L,第一次扩展后是2L-1,第二次是2*(2L-1)-1=4L-3,依此类推。但实际题目中的扩展方式可能有所不同。或者可能每次将字符串的最后一位移到前面,然后拼接。例如,原字符串s,扩展后的字符串是s + rotated(s),其中rotated(s)是将s的最后一个字符移到前面后的字符串。比如COW变成COWWCO,因为原字符串是COW,最后一个字符是W,移到前面变成WCO,所以拼接后是COW + WCO = COWWCO,此时长度是原长度的两倍。但是原题可能每次扩展后的长度是原长度的两倍,而不是两倍减一?这点可能需要再仔细看题目描述。 不过根据题目例子,原字符串是“COW”,长度3。第一次扩展后变成“COWWCO”,长度6。然后第二次扩展变成“COWWCOOCOWWC”吗?或者可能每次扩展后的长度是前一次的两倍?比如原长度是L,每次扩展后的长度是L*2。例如,原长3→6→12→24...等。这样,当N大于当前长度时,可以通过递归的方式找到对应的位置。 假设每次扩展后的字符串长度是前一次的两倍减一?或者可能题目中的扩展方式是:每次将字符串s变为s + s[last] + s[0..last-1],也就是将最后一个字符移到前面,然后拼接。例如,原字符串s,扩展后的字符串是s + last_char + s[0..-2]。比如,COW的扩展是COW + W + CO → COWWCO。这时候,长度是原长度L → L + 1 + (L-1) = 2L。这样的话,每次扩展后的长度是原来的两倍。所以原字符串长度是L,第一次扩展后是2L,第二次是4L,以此类推。那么,当N超过原字符串长度时,可以递归地找到对应的位置。 比如,假设当前的长度是len,当N <= len时,直接返回s[N-1]。否则,我们需要找到在扩展后的字符串中的位置。例如,假设扩展后的字符串是原字符串的两倍长度,那么当N在扩展后的后半部分时,相当于原字符串的某个位置转换后的结果。比如,当原字符串长度是L,扩展后的长度是2L。假设N在L+1到2L之间,那么对应的位置可能是N - L -1的位置在扩展后的后半部分。或者可能需要通过不断将N减去当前长度的一半,直到找到在原始字符串中的位置。 例如,假设当前阶段的字符串长度是L,每次扩展后的长度是2L。那么,当N大于L时,我们可以考虑N所在的区间。比如,如果L < N <= 2L,那么N的位置对应的是扩展后的部分。而扩展后的部分是原字符串最后一个字符加上原字符串的前L-1个字符。例如,原字符串是s,扩展后的后半部分是s[-1] + s[0..-2]。因此,当N在L+1到2L的位置时,这个位置的字符对应于原字符串的位置的计算可能需要调整。 例如,当N是L+1的位置时,对应的是原字符串的最后一个字符。当N是L+2的位置时,对应的是原字符串的第0个字符(假设索引从0开始)。或者,可能需要将N减去L+1,然后递归处理。例如,当N处于后半部分时,N减去L+1得到的位置是k,然后递归处理k的情况。但需要考虑,每次扩展后的结构是否与原结构相似,从而可以用递归或迭代的方法找到对应的位置。 举个例子,假设原字符串是“COW”,长度L=3。扩展后的长度是6。此时,前3个字符是原字符串,后3个字符是“WCO”(将原字符串的最后一个字符W移到前面,然后加上原字符串的前两位CO)。当N是4的话,对应的位置是4-3-1=0?或者可能其他计算方式? 或者,另一种思路是每次扩展后的字符串长度为两倍原来的长度,例如,初始长度是L,扩展一次后是2L,再扩展是4L,依此类推。那么,当N大于当前长度时,我们需要找到当前所在的扩展层级。比如,找到最大的k,使得 L * 2^{k} < N。这可能比较复杂。或者,可以通过不断将当前长度翻倍,直到超过N,然后回溯处理。 具体来说,假设初始长度是len = original_length。我们不断将len乘以2,直到len >= N。此时,每次将len折半,并检查N是否处于后半部分。例如,当len是当前扩展后的长度,而原来的长度是len/2。如果N > len/2,那么这个位置对应于后半部分。此时,后半部分的第一个字符是原字符串的最后一个字符。然后剩下的部分对应于原字符串的前半部分。例如,当len是扩展后的长度,原字符串是s,那么后半部分的结构是 last_char + s[0..-2]。因此,当N处于后半部分的位置时,对应的位置可以转换为在前半部分的某个位置。 举个例子,假设原长度是3,扩展后的长度是6。如果N是5,处于后半部分(4-6的位置)。后半部分的结构是WCO。此时,位置5对应后半部分的第2个字符(从1开始的话,后半部分的起始位置是4,5对应第二个字符)。那么,后半部分的第一个字符是原字符串的最后一个字符W,之后是原字符串的前两个字符C和O。因此,5的位置对应的原字符串的位置是5 - (3) -1 =1,然后递归处理这个位置。或者,可能需要将N减去前半部分长度加一,得到k,然后递归处理k。 比如,假设当前长度是curr_len,当N > curr_len/2时,那么它处于后半部分。此时,如果N等于curr_len/2 +1,对应的字符是原字符串的最后一个字符。否则,N的位置对应于原字符串的位置k = N - (curr_len/2 +1),此时需要递归处理k,然后将结果对应的字符是原字符串k-1的位置,因为后半部分的结构是最后一个字符加上原字符串的前部分。 但这里可能需要处理一些边界情况,比如当k=0的时候,可能需要取原字符串的最后一个字符? 或者,更具体地说: 假设当前阶段的字符串长度是curr_len,每次扩展后的长度是curr_len * 2。那么,当需要找到N的位置时: - 如果 N <= original_len,直接返回s[N-1]。 否则,我们需要找到curr_len,使得 curr_len/2 < N <= curr_len。例如,当curr_len是第一个大于等于N的长度,那么进入循环: while curr_len < N: curr_len *= 2 然后,当curr_len >= N时,我们开始往回退: 每次循环: 如果 curr_len//2 < N <= curr_len: offset = N - curr_len//2 -1 # 因为后半部分的第一个字符是原字符串的最后一个字符,所以当N=curr_len//2+1时,offset是0,对应最后一个字符 if offset ==0: return s[-1] else: N = offset curr_len = curr_len//2 但这里可能需要不断重复这个过程,直到N落在原始字符串的长度范围内。 例如,假设原始字符串长度是3,N=5: 初始curr_len是3。需要扩展直到超过N。第一次扩展后是6,此时5<=6。进入循环: 检查是否 N > curr_len//2 (curr_len是6,curr_len//2是3). 5>3,所以进入后半部分。 offset =5 -3 -1=1. 然后,N=1,此时curr_len=3. 现在检查N是否在原始长度范围内。此时N=1 <=3,所以返回s[0](假设索引从0开始)。 所以结果就是原字符串的第一个字符,即C。但原例子中,扩展后的字符串是COWWCO,当N=5时,应该是后半部分的第5-3=2个位置(后半部分长度为3),即WCO中的第三个字符O?或者可能我的计算方式有问题? 或者,原字符串是COW,第一次扩展后的字符串是COW + WCO → COWWCO。长度是6。那么位置5(索引从1开始的话)是第5个字符,即O。而根据上面的方法计算得到的是原字符串的第0个字符C?这说明我的逻辑有问题。 这说明我的思路有问题,需要重新分析。 正确的扩展方式应该是每次将原字符串的最后一个字符移到前面,然后追加到原字符串后面。比如原字符串是s,长度为L。扩展后的字符串是s + (last_char + s[0..L-2]). 例如,原字符串COW(长度3),扩展后是COW + W(最后一个字符) + CO(前L-1个字符) → COWWCO,长度6。下一次扩展,将最后一个字符O移到前面,得到O + COWWC,然后追加到原字符串后面,得到COWWCOOCOWWC,长度12。 现在,假设我们要找N=5的位置,在第一次扩展后的字符串是COWWCO。各个位置(1开始)的字符是: 1:C, 2:O, 3:W, 4:W, 5:C, 6:O. 所以N=5对应的是C。但根据上面的方法,之前的计算得到的是原字符串的第1个字符(索引0)即C,这与实际结果一致。那之前的例子是正确的。 那这说明,当N=5时,通过递归计算得到的是正确的。那说明我的思路是正确的? 那需要再验证另一个例子。比如,第一次扩展后的字符串是COWWCO,长度为6。假设N=4,对应的字符是W。按照上述方法: curr_len=6,N=4. 判断是否4>3(curr_len//2=3) → 是。offset=4-3-1=0。所以返回原字符串的最后一个字符,即原字符串是COW,最后一个字符是W。所以结果正确。 那这时候,当offset=0时,取原字符串的最后一个字符。而如果offset>0,则新的N是offset,curr_len变为curr_len//2=3。此时,新的N是offset,比如假设原N=5,offset=1,则新的N=1,curr_len=3。此时N=1<=3,所以取原字符串的第0个字符C。正确。 那这说明上述的递归方法是正确的。 因此,算法的步骤可以总结为: 1. 计算原始字符串的长度len,记为original_len。 2. 找到最大的curr_len,使得 curr_len = original_len * 2^k,直到curr_len >= N。然后,不断将curr_len折半,调整N的位置,直到N落在原始长度范围内。 3. 每次当N处于后半段时,即N > curr_len//2,计算offset = N - (curr_len//2) -1。如果offset为0,则返回最后一个字符;否则,将N设为offset,curr_len设为curr_len//2,继续处理。 具体来说,算法的步骤如下: - 初始化curr_len为原始字符串的长度。 - 将curr_len不断翻倍,直到它大于等于N。 - 然后,进入循环,每次处理当前curr_len: while curr_len > original_len: curr_half = curr_len // 2 if N > curr_half: offset = N - curr_half - 1 if offset == 0: N = curr_half else: N = offset curr_len = curr_half - 最后,返回原始字符串的第N-1个字符(因为索引从0开始)。 或者,可能需要更仔细的处理,比如: 例如,当curr_len是当前处理的总长度,如果N > curr_half,则进入后半部分。此时后半部分的第一个字符是原字符串的最后一个字符。剩下的部分是原字符串的前部分。例如,后半部分是last_char + s[0..length-2]。所以,当N等于curr_half +1时,对应的就是last_char。否则,当N在curr_half+2到curr_len时,对应的位置是原字符串的N - (curr_half +1) -1的位置? 或者,可能需要将N转换为对应的位置: 假设原字符串是s,长度L。扩展后的长度是2L。后半部分的结构是last_char + s[0..L-2]. 所以,当在后半部分的位置是p(相对于后半部分的起始位置),则对应的原字符串的位置是p-1(如果p>0的话)。 比如,当N在后半部分的位置是curr_half +1 →对应的是last_char。当N是curr_half+2 →对应的是s[0], curr_half+3→s[1], 以此类推。 因此,当N在后半部分时,我们可以得到: 如果 N == curr_half +1 →返回s的最后一个字符。 否则,新的N等于N - (curr_half +1) →得到后半部分中去掉第一个字符后的位置,即相当于在原字符串的前L-1个字符中的位置。所以新的N' = N - (curr_half +1)。然后,curr_len变为curr_half(原长度的一半?或者原长度是L,curr_half是L,新的长度是L-1?这可能有问题。) 或者,或许更正确的方式是,每次将curr_len折半,然后处理: 当N在后半部分时: 如果 N == curr_half +1 →返回最后一个字符。 否则,N = N - (curr_half +1),然后 curr_len = curr_half -1。因为后半部分的结构是last_char + s[0..curr_half-2]。所以,此时剩余部分的长度是 curr_half-1。例如,原长度是L=3,curr_half=3,后半部分的结构是last_char(W) + s[0..1](CO),总长度3。所以,当N在后半部分且不是第一个位置的话,新的N是N - (curr_half +1) →比如,当原N是5(curr_half=3),5-3-1=1,所以新的N=1,此时curr_len=3-1=2?或者可能需要调整。 这个思路可能需要更仔细的分析。例如,原字符串长度L=3,扩展后的长度是6。curr_half=3。后半部分的长度是3。结构是W(last_char) + CO(前两位)。所以,当N在4到6的位置时: N=4 →对应W,即原字符串的最后一个字符。 N=5 →对应C,即原字符串的第一个字符。 N=6 →对应O,原字符串的第二个字符。 所以,当处理N=5时,需要将N调整为5 -3 -1=1,然后此时curr_len=3,但原来的字符串长度是3,所以这时候新的curr_len应该变为2(因为后半部分的结构是W + CO,即长度为3,其中后两位是前两位的原字符串字符)。 所以,每次处理时,当N在后半部分: 如果N == curr_half +1 →返回最后一个字符。 否则,N = N - (curr_half +1),此时新的curr_len应该是curr_half -1,因为剩下的部分是原字符串的前curr_half-1个字符。例如,原curr_half是3,剩下的部分是CO,长度2。所以新的curr_len是2。 但这样的话,curr_len在每次处理时会逐渐减小,直到等于原字符串长度? 或者,可能这个思路需要将curr_len在每次处理时调整为curr_half-1。例如: 当处理curr_len时,如果N > curr_half: if N == curr_half +1: 返回最后一个字符 else: N = N - (curr_half +1) curr_len = curr_half -1 这样,当原curr_len是6,处理N=5: curr_half=3 →5>3 →检查是否等于4?不是。N=5-3-1=1 →新的N=1,curr_len=3-1=2. 现在,再次处理: curr_len=2,curr_half=1. 此时,N=1 <=1 →返回原字符串的第0个字符? 但原字符串是COW,而这里curr_len被调整为2,这可能不正确,因为原字符串的长度是3,而这里在递归过程中curr_len可能变成了2? 这可能说明这种处理方式有问题。需要重新考虑。 可能正确的处理方式是,每次扩展后的字符串长度是前一次的两倍。例如,初始长度是L=3,第一次扩展后是6,第二次是12,依此类推。此时,每次的curr_len是这些长度。当处理时,如果N在后半部分,那么我们需要找到对应的位置。 例如,当curr_len=6,处理N=5: curr_half=3. 因为5>3,所以进入后半部分。后半部分的结构是原字符串的最后一个字符(W)加上原字符串的前两个字符(CO)。 所以,后半部分的长度为3,其中第一个字符是W,后面两个是C、O。 此时,当N=5,即在后半部分中的位置是5-3=2(相对于后半部分的起始位置是4,即第4、5、6三个位置,N=5是后半部分的第二个位置)。所以,对应的字符是后半部分中的第二个字符,即C。 那么,如何将N=5转换为原字符串中的位置? 后半部分的结构是:W(位置4),C(位置5),O(位置6)。 如果N=5,对应的后半部分中的索引是1(从0开始),那么对应的原字符串中的位置是0(因为后半部分的结构是W + s[0] + s[1])。 因此,当N在后半部分时,计算方式应为: 如果N在后半部分,那么计算相对位置为N - curr_half。如果相对位置是0,则对应的是原字符串的最后一个字符。否则,相对位置减1得到原字符串中的位置。例如: N=4 →相对位置0 →原字符串最后一个字符。 N=5 →相对位置1 →原字符串的位置0. N=6 →相对位置2 →原字符串的位置1. 这样,当处理N在后半部分时,可以通过以下方式调整: offset = N - curr_half if offset ==0: new_N = curr_half -1 (因为原字符串的长度是curr_half,最后一个字符的索引是curr_half-1) else: new_N = offset -1 然后,将curr_len设为curr_half,继续处理新的_N。 例如,curr_half是3: 当N=5,offset=5-3=2. new_N=2-1=1. curr_len=3. 现在,再次处理N=1,curr_len=3(原字符串长度是3)。此时N<=3,返回s[0]. 所以,得到的字符是C,正确。 另一个例子,N=4: offset=4-3=1 →因为offset不是0,所以new_N=1-1=0. curr_len=3. 此时N=0?或者可能我的索引方式有问题? 或者,当处理时,原字符串的索引是0-based,而N是1-based? 例如,原问题中的N是1-based的。比如,原字符串的字符位置是1到L。 因此,当转换到原字符串中的位置时,需要保持1-based。 例如,原字符串的长度是3,字符是1:C, 2:O,3:W. 当处理N=4时: curr_half=3 →offset=4-3=1. 因为offset>0 →new_N=1-1=0?或者可能我的转换方式错误? 或者,此时在后半部分的结构是:位置4是W(原字符串的3),位置5是C(原字符串的1),位置6是O(原字符串的2). 所以,当N=5(对应后半部分的第二个位置),转换后的新N是1,即原字符串的第一个位置,返回C。 当N=4(后半部分第一个位置),转换后的new_N是0?或者,这时候,当offset=1,new_N是offset-1=0,此时在curr_len=3时,N=0是不正确的,因为N应该是1-based的。 这说明,我的转换逻辑存在错误。需要重新分析。 正确的转换方式可能应该是: 当N在后半部分时: - 后半部分的第一个字符是原字符串的最后一个字符(位置L)。 - 后半部分的第二个字符是原字符串的第一个字符(位置1)。 - 后半部分的第三个字符是原字符串的第二个字符(位置2)。 依此类推,直到填满后半部分。 所以,当N在后半部分的位置为k(相对于后半部分起始位置是curr_half+1),则对应的原字符串的位置是: 如果k=1 →最后一个字符(原位置L)。 否则,k>1 →对应的原字符串位置是k-2(因为后半部分的第二个字符对应原字符串的第一个字符)。 或者,可能需要将N转换为在原字符串中的位置。例如: 在后半部分,总共有curr_half个字符。其中第一个字符是原字符串的最后一个字符,剩下的curr_half-1个字符是原字符串的前curr_half-1个字符。例如,原字符串长度是L=3,后半部分的长度是3,结构是W(原3),C(原1),O(原2)。 因此,当N在后半部分中的位置是相对于curr_half+1的,比如: N=4 →后半部分的第一个字符 →原3. N=5 →后半部分的第二个字符 →原1. N=6 →后半部分的第三个字符 →原2. 所以,当N处于后半部分时,可以计算: k = N - curr_half. 如果k ==1 →原字符串的最后一个字符(位置L). 否则,k>1 →原字符串的位置是k-1 -1 =k-2?或者可能是k-1的位置? 或者,k的取值是1到curr_half: 比如,当curr_half=3,后半部分的k=1对应原3,k=2对应原1,k=3对应原2. 所以,当k=1 →原L. 当k>1 →原k-1 -1?或者原k-2? 或者,可以发现,当k>1时,对应的是原字符串的k-2的位置? 例如,k=2 →原0(假设0-based) →C? 这显然有问题,因为原字符串的0-based是C(索引0)。 所以,可能正确的转换方式是将k>1时,对应的原位置是(k-2) % (L-1),其中L是原字符串的长度? 或者,这可能比较复杂。另一种思路是,当处理N在后半部分时: 如果k=1 →原L-1(0-based). 否则,新的N =k-2,并在原字符串的前L-1个字符中查找。 例如,原字符串长度是3,后半部分的k=2对应原字符串的0,k=3对应原字符串的1. 这样,当处理N=5时: k=5-3=2 →新的N=2-1=1 →原字符串的索引0. 这可能更合理。 这似乎比较复杂,所以需要找到一个通用的方法,将N在后半部分的位置转换为原字符串中的位置。 正确的做法可能是在每次处理时,当N在后半部分时: 1. 如果N等于curr_half +1 →返回原字符串的最后一个字符。 2. 否则,将N设置为N - (curr_half +1),然后继续处理,此时curr_len变为curr_half -1. 比如,原字符串长度是3,curr_half=3,N=5 →5 -3-1=1 →新的N=1,curr_len=3-1=2. 此时,curr_len是2,而原字符串的长度是3,这显然有问题,因为curr_len应该指的是扩展后的字符串长度。这似乎不正确。 或者,可能需要保留原字符串的长度不变,而只调整当前处理的长度。例如,每次扩展后的长度是原来的两倍,而原字符串的长度始终是original_len。所以,当处理到某个阶段,curr_len是扩展后的长度,当N在后半部分时,需要将其转换为原字符串的结构。 这可能意味着,每次调整时,curr_len是当前扩展后的长度,而原字符串的长度保持不变。因此,当处理到后半部分时,新的N需要调整为对应到原字符串的前L-1个字符中的位置。 这可能需要更仔细的数学推导。 假设原字符串的长度是L。每次扩展后的长度是2^k * L,其中k是扩展次数。例如,扩展一次后长度是2L,两次是4L,等等。当N处于某个扩展后的长度中,即处于区间 (2^{k-1} L, 2^k L]时,需要找到对应的位置。 此时,每次处理时,我们需要将当前的curr_len(当前扩展后的长度)除以2,得到之前的扩展后的长度。然后,判断N是否在后半部分。如果是,则需要调整N。 例如,当curr_len=2^k L时,前半部分的长度是curr_half=2^{k-1} L. 后半部分的长度也是curr_half. 但根据扩展规则,后半部分的结构是原字符串的最后一个字符,加上原字符串的前curr_half-1个字符。因此,当N处于后半部分时,对应的位置可以通过以下步骤计算: 假设当前curr_len=2^m L。当前处理的是第m次扩展后的字符串。当N在后半部分时: 位置在curr_half+1到curr_len之间。 对于这些位置,第一个字符是原字符串的最后一个字符。后续的字符对应于原字符串的前curr_half-1个字符,但每次扩展后的结构可能递归地应用同样的规则。 因此,处理的方法可以是: 当N>curr_half时: 如果N == curr_half +1 →返回原字符串的最后一个字符。 否则,调整N为N - (curr_half +1),然后curr_len变为 curr_half -1。 因为后半部分的结构是最后一个字符,加上原字符串的前curr_half-1个字符,所以此时相当于进入一个长度为curr_half-1的新字符串。 这可能不正确,因为原字符串的长度是固定的,而每次扩展后的长度是前一次的两倍。所以,当调整curr_len时,应该将其设为 curr_half,而不是 curr_half-1. 或者,可能这部分的处理需要更仔细的调整。例如: 当curr_len=2^m L,curr_half=2^{m-1} L. 当N>curr_half: if N == curr_half +1 →返回原字符串的最后一个字符。 else →新的N是 N - (curr_half +1), 并且curr_len = curr_half -1. 这似乎会导致curr_len减少到curr_half-1,这可能与原扩展后的长度不匹配。例如,当原扩展后的长度是2^m L,而curr_half=2^{m-1} L。此时,curr_half-1=2^{m-1} L -1,这可能不是扩展后的有效长度。 这表明,或许我的思路有错误,需要重新考虑。 另一种思路是,将问题看作递归的。例如,当N处于扩展后的后半部分时,可以通过不断减小N的方式,找到其在原字符串中的对应位置。 例如,初始字符串长度为L。每次扩展后,字符串长度翻倍。当N大于L时,我们可以逐步将N减少到L范围内。 具体来说,假设当前扩展后的字符串长度是L*2^k。我们需要找到k,使得L*2^{k-1} < N <= L*2^k. 然后,处理N: 当N > L*2^{k-1}: offset = N - L*2^{k-1} if offset ==1: return s[-1] else: new_N = offset -1 然后,处理new_N,其中现在k减少1,即新的扩展次数是k-1,当前扩展后的长度是L*2^{k-1}. 重复这一过程,直到新的_N落在L的范围内,然后返回s[new_N-1]. 例如,原字符串长度L=3,N=5. 初始扩展后的长度是6(k=1,因为 3*2^1=6). N=5 >3*2^0=3. offset=5-3=2. 因为 offset !=1,所以 new_N=2-1=1. 现在,处理new_N=1,此时扩展次数k=0,字符串长度3. 1<=3,返回s[0],即C。正确。 另一个例子,N=4: offset=4-3=1 →返回s[-1]即W。正确。 再比如,N=6: offset=6-3=3 →new_N=3-1=2 →处理new_N=2,返回s[1]即O。正确。 这表明这种方法是可行的。所以,算法的大致步骤如下: 1. 计算原字符串的长度L。 2. 如果N <=L,直接返回s[N-1]. 3. 否则,找到最大的k,使得 L*2^{k} < N。或者,可以不断将当前长度翻倍,直到超过N,然后回溯处理。 但更高效的做法是,用循环来模拟这一过程: 初始化当前长度curr_len为L. while curr_len < N: curr_len *=2 然后,进入循环: while curr_len > L: curr_len_half = curr_len //2 if N > curr_len_half: offset = N - curr_len_half if offset ==1: N= curr_len_half else: N=offset-1 curr_len = curr_len_half 最后,返回s[N-1] 这似乎可以得到正确的结果。 例如,测试原字符串COW,L=3: N=4: 初始化curr_len=3→3<4,翻倍到6. 进入循环: curr_len=6>3. curr_half=3. N=4>3 →offset=4-3=1 →等于1 →N=3. curr_len=3. 循环结束。返回s[3-1] →s[2]=W。正确吗?原字符串是COW,s[2]是W,而N=4对应扩展后的字符串的第四个字符,即W。正确。 另一个例子,N=5: curr_len初始3→翻倍到6. 处理curr_len=6: N=5>3 →offset=5-3=2 !=1 →N=2-1=1. curr_len=3. 返回s[0]=C。正确。 N=6: curr_len=6 →处理: offset=6-3=3 →!=1 →N=3-1=2 →返回s[1]=O. 正确。 这说明这个算法是正确的。 因此,C语言代码的大致结构是: 读取字符串s和N. 计算L = strlen(s). 如果N <=L →输出s[N-1]. 否则: curr_len = L; while (curr_len < N) curr_len *=2; while (curr_len > L) { curr_half = curr_len/2; if (N > curr_half) { offset = N - curr_half; if (offset ==1) { N = curr_half; } else { N = offset -1; } } curr_len = curr_half; } printf("%c", s[N-1]); 需要注意的边界条件包括: 1. 当N等于curr_half+1时,即offset=1,此时N被设置为curr_half,这可能超过原字符串的长度吗?例如,当curr_half是原字符串的长度,此时将N设置为curr_half等于原字符串的长度。例如,原字符串长度是3,curr_half=3,N=4→offset=1→N=3,此时curr_len=3,判断curr_len>3不成立,返回s[2]. 所以是正确的。 另一个测试案例,原字符串长度为1,比如s="A",N=2. 处理: L=1. curr_len=1→翻倍到2. 进入循环: curr_half=1. N=2>1 →offset=2-1=1 →N=1. curr_len=1. 退出循环,返回s[0],即A。这正确吗? 扩展后的字符串是"A" →第一次扩展后的字符串是A + A的前半部分?或者原字符串是A,扩展后的字符串是AA?原扩展规则是将最后一个字符(A)放到前面,然后追加到原字符串后面,所以扩展后的字符串是A + A →AA。所以N=2的字符是A。正确。 另一个例子,原字符串是ABCD,长度4。第一次扩展后变成ABCD DABC(假设最后一次字符是D,移到前面,所以新部分是 DABC →ABCDDABC,长度8. 假设N=5→D,N=6→A,N=7→B,N=8→C. 测试N=6: L=4. curr_len=4 →翻倍到8. 处理: curr_half=4. N=6>4 →offset=6-4=2 →offset !=1 →N=2-1=1. curr_len=4. 退出循环,返回s[0]即A. 正确吗? 原字符串是ABCD,扩展后的字符串是ABCDDABC。N=6对应的字符是A,正确。 所以算法正确。 综上所述,C语言的实现需要按照上述步骤进行。</think>要解决洛谷P3612题“Secret Cow Code”,我们需要找到按特定规则无限扩展后的字符串中第N个字符。由于直接模拟扩展过程不可行,需采用递归或迭代方法高效定位字符。 ### 题目分析 扩展规则:每次将当前字符串的最后一个字符移到最前面,然后将这个新字符串追加到原字符串后面。例如,原字符串为“COW”,第一次扩展后为“COW” + “WCO” → “COWWCO”。 ### 关键思路 1. **长度倍增**:每次扩展后字符串长度变为原来的两倍。 2. **递归定位**:当N超过当前长度的一半时,调整N至前半部分对应的位置,直至N位于原字符串范围内。 ### 算法步骤 1. **初始化**:计算原字符串长度`L`。 2. **扩展长度**:找到最小的扩展后长度`curr_len`,使其不小于N。 3. **逆推定位**:逐步将`curr_len`折半,调整N的位置: - 若N在后半部分,计算其在前半部分的对应位置。 - 若N恰好在后半段的第一个位置,对应原字符串的最后一个字符。 ### 代码实现 ```c #include <stdio.h> #include <string.h> int main() { char s[31]; // 题目保证字符串长度不超过30 long long N; scanf("%s %lld", s, &N); long long L = strlen(s); long long curr_len = L; // 扩展curr_len直到超过N while (curr_len < N) { curr_len *= 2; } // 逆推定位N while (curr_len > L) { long long half = curr_len / 2; if (N > half) { long long offset = N - half; if (offset == 1) { N = half; } else { N = offset - 1; } } curr_len = half; } printf("%c\n", s[N - 1]); return 0; } ``` ### 代码解释 1. **输入处理**:读取原字符串s和目标位置N。 2. **长度扩展**:通过不断倍增`curr_len`,找到首个不小于N的长度。 3. **位置逆推**: - 当N位于后半段时,调整N至前半段的对应位置。 - 若N在后半段的第一个位置(即`offset == 1`),则直接定位到原字符串的最后一个字符。 4. **输出结果**:最终N落在原字符串范围内,直接输出对应字符。 ### 示例分析 以原字符串“COW”和N=5为例: 1. 初始长度L=3,扩展后`curr_len=6`(3→6)。 2. N=5 > 3(`half=3`)→ `offset=5-3=2` → N调整为`2-1=1`。 3. `curr_len`折半为3,此时N=1 ≤3,返回s[0]即‘C’。 该算法通过数学推导高效定位字符,避免了暴力扩展,时间复杂度为O(log(N/L))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值