2017校赛 问题 D: 我知道了,你知道了吗?【递归】
题目描述
Alice和Bob走在去学校的路上,听到两个路人的对话:
路人甲:我知道了, 你知道了吗?
路人乙:我知道你知道了,你知道了吗?
路人甲:我知道你知道我知道了,你知道了吗?
路人乙:我知道你知道我知道你知道了,你知道了吗?
.........
他们便觉得十分有趣,觉得非常像两个人在玩字符串拼接的游戏,受此启发,Alice和Bob玩起了”类似”的拼接游戏:
Alice首先会选择四个字符串S1, A, B, C,之后Alice和Bob轮流拼接出第2,第3,第4....第n个字符串,对于第i个字符串Si (i > 1), 它的拼接规则是Si = A + Si-1 + B + Si-1 + C,其中符号“+”是字符串连接的意思,举个例子:
Alice先选择了S1 = “ab”, A = “CD”, B = “EF”, C = “GH”,接下来的操作是:
Bob 拼接出 S2 = “CDabEFabGH”
Alice拼接出 S3 = “CDCDabEFabGHEFCDabEFabGHGH”
...........
走在路上的你恰巧看到了Alice和Bob玩游戏的整个过程,Alice就问你,在他们拼出的第n个字符串中的第k个字符是什么?Alice和Bob都已经知道了,你知道了吗?假设无论多长的字符串Alice和Bob都能拼接出来。
注意引号是定界符,不属于字符串。
输入
输入包括多组数据(数据组数总共不超过50)。
每组第一行有四个字符串,分别是S1, A, B, C(长度都<=100),字符串只包含小写英文字母和大写英文字母.
第二行一个整数q(1 <= q <= 50),表示Alice询问的次数。
接下来有q行, 每组两个整数n(1 <= n <= 1000)和k(1 <= k <= 100000000),分别代表Alice和Bob拼接出的第n个字符串以及第n个字符串中的第k个字符。
输出
对于每个n和k,输出对应的Alice和Bob拼接出的第n个字符串中第k个字符。如果第n个字符串中第k个字符不存在输出’*’。每个答案独占一行。
样例输入
ab CD EF GH 5 1 1 1 2 1 3 2 10 2 11
样例输出
a b * H *
越补题越崩溃,点背加经验少。。。。
记录下个人思路:
显然第n个字符串是个递推式:
那么我求第n个字符串的第k个字符只需要判断k的范围即可,如果k<=len(A),就在A中找相应位置,如果k>len(A)&&k<=len(Sn-1)就递归到Sn-1,在Sn-1中找第k-len(A)个位置,以此类推。。。。
所以这道题刚开始我这样写:
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #include<cmath> 6 using namespace std; 7 int ls,la,lb,lc; 8 int s[1006]; 9 char s1[105],A[105],B[105],C[105]; 10 11 void solve(int n,int k) 12 { 13 if(n==1){ 14 printf("%c\n",s1[k-1]); 15 return; 16 } 17 if(k<=la){ 18 printf("%c\n",A[k-1]); 19 return; 20 }else if(k>la+s[n-1]&&k<=la+s[n-1]+lb){ 21 printf("%c\n",B[k-la-s[n-1]-1]); 22 return; 23 }else if(k>la+lb+2*s[n-1]&&k<=la+lb+lc+2*s[n-1]){ 24 printf("%c\n",C[k-la-lb-2*s[n-1]-1]); 25 return; 26 }else if(k>la&&k<=la+s[n-1]){ 27 solve(n-1,k-la); 28 return; 29 }else { 30 solve(n-1,k-la-lb-s[n-1]); 31 return; 32 } 33 } 34 35 int main() 36 { 37 while(scanf("%s%s%s%s",s1,A,B,C)==4) 38 { 39 memset(s,0,sizeof(s)); 40 ls=strlen(s1);la=strlen(A); 41 lb=strlen(B);lc=strlen(C); 42 s[1]=ls; 43 for(int i=2;i<=1001;i++){ 44 s[i]=s[i-1]*2+la+lb+lc; 45 } 46 int q,n,k; 47 scanf("%d",&q); 48 while(q--) 49 { 50 scanf("%d%d",&n,&k); 51 if(k>s[n]) printf("*\n"); 52 else{ 53 solve(n,k); 54 } 55 } 56 } 57 return 0; 58 } 59
显然,运行错误(段错误),就是超数组边界了。后来再想想,len(Sn)=2*len(Sn-1)+len(A)+len(B)+len(C)==>len(Sn)=2^(n-1)len(s1)+(4*n-5)*[len(A)+len(B)+len(C)](大概推了一下,可能有错误)。可以看出,字符串的长度是呈指数形式增长的,自然存到n=30数就已经很大很大了,又有k<=10^8,那么也就是说即便S1,A,B,C长度均为1,n==28时已经大于这个数了(2^27约为134217728)。所以当n>=28时长度已经足够达到k的最大值。所以我只存到n=28,当n>28时根据递推式推到n<=28的范围内再用上面的做法解决。
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #include<cmath> 6 using namespace std; 7 typedef long long ll; 8 ll ls,la,lb,lc; 9 ll s[31]; 10 char s1[105],A[105],B[105],C[105]; 11 12 void solve(int n,int k) 13 { 14 if(n==1){ 15 printf("%c\n",s1[k-1]); 16 return; 17 } 18 if(k<=la){ 19 printf("%c\n",A[k-1]); 20 return; 21 }else if(k>la+s[n-1]&&k<=la+s[n-1]+lb){ 22 printf("%c\n",B[k-la-s[n-1]-1]); 23 return; 24 }else if(k>la+lb+2*s[n-1]&&k<=la+lb+lc+2*s[n-1]){ 25 printf("%c\n",C[k-la-lb-2*s[n-1]-1]); 26 return; 27 }else if(k>la&&k<=la+s[n-1]){ 28 solve(n-1,k-la); 29 return; 30 }else { 31 solve(n-1,k-la-lb-s[n-1]); 32 return; 33 } 34 } 35 36 int main() 37 { 38 while(scanf("%s%s%s%s",s1,A,B,C)==4) 39 { 40 memset(s,0,sizeof(s)); 41 ls=strlen(s1);la=strlen(A); 42 lb=strlen(B);lc=strlen(C); 43 s[1]=ls; 44 for(int i=2;i<=29;i++){ 45 s[i]=s[i-1]*2+la+lb+lc; 46 } 47 int q,n,k; 48 scanf("%d",&q); 49 while(q--) 50 { 51 scanf("%d%d",&n,&k); 52 int pos=1; 53 for(int i=1;i<=28;i++) 54 if(s[i]>=k){ 55 pos=i;break; 56 } 57 if(pos>n) printf("*\n"); 58 else if(n<=28){ 59 solve(n,k); 60 }else { 61 int t=la*(n-28); 62 if(t>=k){ 63 int r=k%la; 64 printf("%c\n",A[r]); 65 }else{ 66 solve(28,k-t); 67 } 68 } 69 } 70 } 71 return 0; 72 }