洛谷 P3071 [USACO13JAN]座位Seating-线段树区间合并(判断找,只需要最大前缀和最大后缀)+分治+贪心...

本文探讨了USACO竞赛中的一道经典题目——座位安排问题。问题要求在一排座位上进行一系列操作,包括将一组客人安排在连续的空位上,以及清除指定范围内的客人。目标是计算无法安排的客人组数。文章提供了详细的代码实现,采用线段树数据结构进行高效处理。

P3071 [USACO13JAN]座位Seating

题目描述

To earn some extra money, the cows have opened a restaurant in their barn specializing in milkshakes. The restaurant has N seats (1 <= N <= 500,000) in a row. Initially, they are all empty.

Throughout the day, there are M different events that happen in sequence at the restaurant (1 <= M <= 300,000). The two types of events that can happen are:

  1. A party of size p arrives (1 <= p <= N). Bessie wants to seat the party in a contiguous block of p empty seats. If this is possible, she does so in the lowest position possible in the list of seats. If it is impossible, the party is turned away.

  2. A range [a,b] is given (1 <= a <= b <= N), and everybody in that range of seats leaves.

Please help Bessie count the total number of parties that are turned away over the course of the day.

有一排n个座位,m次操作。A操作:将a名客人安置到最左的连续a个空位中,没有则不操作。L操作:[a,b]的客人离开。

求A操作的失败次数。

输入输出格式

输入格式:

 

* Line 1: Two space-separated integers, N and M.

* Lines 2..M+1: Each line describes a single event. It is either a line of the form "A p" (meaning a party of size p arrives) or "L a b" (meaning that all cows in the range [a, b] leave).

 

输出格式:

 

* Line 1: The number of parties that are turned away.

 

输入输出样例

输入样例#1:  复制
10 4 
A 6 
L 2 4 
A 5 
A 2 
输出样例#1:  复制
1 

说明

There are 10 seats, and 4 events. First, a party of 6 cows arrives. Then all cows in seats 2..4 depart. Next, a party of 5 arrives, followed by a party of 2.

Party #3 is turned away. All other parties are seated.

 

代码:

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 typedef long long ll;
  4 const int maxn=5e6+10;
  5 #define lson l,m,rt<<1
  6 #define rson m+1,r,rt<<1|1
  7 
  8 struct Tree{
  9     int lch,rch,val,lazy;
 10 }tree[maxn<<2];
 11 
 12 void pushup(int l,int r,int rt)
 13 {
 14     int m=(l+r)>>1;
 15     if(tree[rt<<1].val==(m-l+1)) tree[rt].lch=tree[rt<<1].val+tree[rt<<1|1].lch;
 16     else tree[rt].lch=tree[rt<<1].lch;
 17     if(tree[rt<<1|1].val==(r-m)) tree[rt].rch=tree[rt<<1|1].val+tree[rt<<1].rch;
 18     else tree[rt].rch=tree[rt<<1|1].rch;
 19     tree[rt].val=max(max(tree[rt<<1].val,tree[rt<<1|1].val),tree[rt<<1].rch+tree[rt<<1|1].lch);
 20 }
 21 
 22 void pushdown(int l,int r,int rt)
 23 {
 24     int m=(l+r)>>1;
 25     if(tree[rt].lazy){
 26         if(tree[rt].lazy==1){//清空
 27             tree[rt<<1].lazy=tree[rt<<1|1].lazy=tree[rt].lazy;
 28             tree[rt<<1].lch=tree[rt<<1].rch=tree[rt<<1].val=m-l+1;
 29             tree[rt<<1|1].lch=tree[rt<<1|1].rch=tree[rt<<1|1].val=r-m;
 30         }
 31         if(tree[rt].lazy==2){//坐满
 32             tree[rt<<1].lazy=tree[rt<<1|1].lazy=tree[rt].lazy;
 33             tree[rt<<1].lch=tree[rt<<1].rch=tree[rt<<1].val=0;
 34             tree[rt<<1|1].lch=tree[rt<<1|1].rch=tree[rt<<1|1].val=0;
 35         }
 36         tree[rt].lazy=0;
 37     }
 38 }
 39 
 40 void build(int l,int r,int rt)
 41 {
 42     tree[rt].lazy=0;
 43     if(l==r){
 44         tree[rt].lch=tree[rt].rch=tree[rt].val=1;
 45         return ;
 46     }
 47 
 48     int m=(l+r)>>1;
 49     build(lson);
 50     build(rson);
 51     pushup(l,r,rt);
 52 }
 53 
 54 void update(int L,int R,int c,int l,int r,int rt)
 55 {
 56     if(tree[rt].lazy){
 57         pushdown(l,r,rt);
 58     }
 59 
 60     if(L<=l&&r<=R){
 61         if(c==1){
 62             tree[rt].lch=tree[rt].rch=tree[rt].val=r-l+1;
 63         }
 64         if(c==2){
 65             tree[rt].lch=tree[rt].rch=tree[rt].val=0;
 66         }
 67         tree[rt].lazy=c;
 68         return ;
 69     }
 70 
 71     int m=(l+r)>>1;
 72     if(L<=m) update(L,R,c,lson);
 73     if(R> m) update(L,R,c,rson);
 74     pushup(l,r,rt);
 75 }
 76 
 77 int query(int c,int l,int r,int rt)
 78 {
 79     if(tree[rt].lazy){
 80         pushdown(l,r,rt);
 81     }
 82 
 83     if(l==r){
 84         return l;
 85     }
 86 
 87     int m=(l+r)>>1;
 88     if(tree[rt<<1].val>=c) return query(c,lson);
 89     else if(tree[rt<<1].rch+tree[rt<<1|1].lch>=c) return m-tree[rt<<1].rch+1;
 90     else return query(c,rson);
 91 }
 92 
 93 int main()
 94 {
 95     int n,m;
 96     scanf("%d%d",&n,&m);
 97     build(1,n,1);
 98     int num=0;
 99     for(int i=1;i<=m;i++){
100         char op[5];
101         scanf("%s",op);
102         if(op[0]=='A'){
103             int x;
104             scanf("%d",&x);
105             if(tree[1].val>=x){
106                 int ans=query(x,1,n,1);
107                 update(ans,ans+x-1,2,1,n,1);
108             }
109             else num++;
110         }
111         else{
112             int l,r;
113             scanf("%d%d",&l,&r);
114             update(l,r,1,1,n,1);
115         }
116     }
117     printf("%d\n",num);
118 }

 

转载于:https://www.cnblogs.com/ZERO-/p/10733403.html

AI-PPT 一键生成 PPT:用户输入主题关键词,AI-PPT 可快速生成完整 PPT,涵盖标题、正文、段落结构等,还支持对话式生成,用户可在 AI 交互窗口边查看边修改。 文档导入转 PPT:支持导入 Word、Excel、PDF 等多种格式文档,自动解析文档结构,将其转换为结构清晰、排版规范的 PPT,有保持原文和智能优化两种模式。 AI-PPT 对话 实时问答:用户上传 PPT 或 PPTX 文件后,可针对演示内容进行提问,AI 实时提供解答,帮助用户快速理解内容。 多角度内容分析:对 PPT 内容进行多角度分析,提供全面视野,帮助用户更好地把握内容结构和重点。 多语言对话支持:支持多语言对话,打破语言障碍,方便不同语言背景的用户使用。 AI - 绘图 文生图:用户输入文字描述,即可生成符合语义的不同风格图像,如油画、水彩、中国画等,支持中英文双语输入。 图生图:用户上传图片并输入描述,AI - 绘图能够根据参考图和描述生成新的风格化图像,适用于需要特定风格或元素的创作需求。 图像编辑:提供如 AI 超清、AI 扩图、AI 无痕消除等功能,用户可以上传图片进行细节修改和优化,提升图片质量。 AI - 文稿 文案生成:能够根据用户需求生成多种类型的文章,如市场营销文案、技术文档、内部沟通内容等,提升文案质量和创作效率。 文章润色:对已有文章进行改善和优化,包括语言表达、逻辑连贯性、内容流畅度等方面,使文章更符合用户期望和风格。 文章续写:AI 技术理解文本语境,为用户提供新的想法、补充资料或更深层次的见解,帮助用户丰富文档内容。 AI - 医生 智能健康咨询:包括症状自查,用户输入不适症状,AI 结合病史等信息提供疾病可能性分析与初步建议;用药指导,支持查询药品适应症、禁忌症等,并预警潜在冲突;中医辨证,提供体质辨识与调理建议。 医学报告解读:用户上传体检报告
<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、付费专栏及课程。

余额充值