19 约瑟夫环2
问题描述 :
明明是一名公安局的谈判专家,专门负责和绑匪谈判。有一次,明明接到一个特殊的任务,他赶到了案发现场,发现有k个绑匪绑架了k个人质,于是明明就开始和绑匪进行谈判。绑匪提出了一个非常特殊的要求,如果明明能够回答出这个问题,那绑匪将释放所有的人质;否则,绑匪就要撕票。 绑匪的问题是这样:绑匪把人质和自己围成一个圈,把人质从1开始编号,一直编到k,然后绑匪自己从k+1开始编号,一直编到2k。现在从编号1开始,每次从其中选出第m个人(隔m-1选出一个人)出列,然后绑匪要求明明选定这个m值,且m值要尽量的小,使得最先出列的k个人都是绑匪。 例如:有3个坏人和3个人质,他们排成一圈,其中编号1到3的为人质,编号4到6的为坏人,如下: 1、2、3、4、5、6; 明明要选定m=5时,能够满足绑匪的要求。因为: 第一轮,从1开始数,编号5出列,剩下的人为: 1、2、3、4、6; 第二轮,从6开始数,编号4出列,剩下的人为: 1、2、3、6; 第三轮,从6开始数,编号6出列,剩下的人为: 1、2、3; 这样所有的绑匪都先出列,明明可以成功地救出所有的人质。 如果明明能够找到这个m值,那么所有的人质都想获救,否则的话,后果不堪设想。明明意识到了问题的严重,这个问题对他来说十分地棘手。于是明明想到了你,你是一名程序设计专家,明明想让你用程序来解这个问题,救出所有的人质。 明明的问题可以归结为:假设有k个人质和k个绑匪围成一圈。人质的编号从1到k,绑匪的编号从k+1到2k。从编号1开始,每次从其中选出第m个人(隔m-1选出一人)出列。希望求出m的最小值,使得最先出列的k个人都是绑匪,即都是编号从k+1到2k的人。
输入说明 :
你写的程序要求从标准输入设备中读入测试数据作为你所写程序的输入数据。标准输入设备中有多组测试数据,每组测试数据仅一行,每组测试数据有一个整数k(1≤k≤10),表示人质的人数和绑匪的人数。每组测试数据与其后一组测试数据之间没有任何空行,第一组测试数据前面以及最后一组测试数据后面也都没有任何空行。
输出说明 :
对于每一组测试数据,你写的程序要求计算出一组相应的运算结果,并将这一组运算结果作为你所写程序的输出数据依次写入到标准输出设备中。每组运算结果为一个整数m,即明明要选定的那个数。每组运算结果单独形成一行数据,其行首和行尾都没有任何空格,每组运算结果与其后一组运算结果之间没有任何空行,第一组运算结果前面以及最后一组运算结果后面也都没有任何空行。 注:通常,显示屏为标准输出设备。
题目的解题思路很暴力,暴力循环,直到找到满足题目要求的解。
核心代码:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <math.h> 4 #include <string.h> 5 #include <ctype.h> 6 7 8 9 int record[25]; 10 11 void InitRecord(int k); 12 int ValueOfM(int mod,int k); 13 int nextIdx(int idx,int step,int mod); 14 15 16 int main(){ 17 18 int k,i,j,res,mod; 19 20 while(scanf("%d",&k)!=EOF){ 21 //k个坏人,k个人质 22 mod = 2*k; 23 res = ValueOfM(mod,k); 24 printf("%d\n",res); 25 26 } 27 return 0; 28 } 29 30 31 32 void InitRecord(int k){ 33 int i; 34 for(i=0;i<2*k;i++){ 35 record[i] = i+1; 36 } 37 } 38 39 40 41 int ValueOfM(int mod,int k){ 42 int res; 43 //初始化数组 44 InitRecord(k); 45 int count,step,flag,idx,i; 46 47 step = k; 48 idx = 0;//当前人 49 flag=-1;//定义第一次失败 50 count = 0; 51 int left = 2*k; 52 while(1){ 53 54 55 if(flag==0){ 56 idx = nextIdx(idx,step,left); 57 } 58 59 if(flag==-1){ 60 //重新开始 61 InitRecord(k); 62 count=0; 63 idx =0; 64 left = 2*k; 65 idx = nextIdx(idx,step,left); 66 67 } 68 if(flag==1){ 69 break; 70 } 71 //外层循环 72 //printf 73 if(record[idx]>k){ 74 //找到了 75 for(i=idx;i<left-1;i++){ 76 record[idx] = record[idx+1]; 77 } 78 count++;//统计找到人数 79 left--; 80 if(count==k){ 81 flag=1;//完成任务break; 82 res = step+1; 83 }else{ 84 flag=0; 85 } 86 }else if(record[idx]>0){ 87 //不符合,触雷,重新开始 88 step++; 89 flag=-1; 90 }else{ 91 //已经标记过 debug 92 printf("mark----------------------------\n"); 93 } 94 95 } 96 return res; 97 98 } 99 100 101 int nextIdx(int idx,int step,int mod){ 102 idx = (idx+step)%mod; 103 return idx; 104 }
刚开始我的nextIdx函数的写法十分的耗费时间:
第一版的nextIdx函数如下:
int nextIdx(int idx,int step,int mod){ while(record[idx]<0){ idx++; idx = idx%mod; } if(idx==-1){ idx=0; } int n = step; while(n){//按照规则走step步 if(record[idx]>0){ printf("n=%d\n",n); n--; } idx++; idx = idx%mod; } while(record[idx]<0){ idx++; idx = idx%mod; } return idx; }
这种写法基于给每个数组指定一种状态,即是否已经淘汰(用-1标记)。然而在实际运行时,假设step很大,且中间有连续很多个人已经出局
那么while(n)代码块非常耗时。
解决思路:
将已经淘汰的直接从数组中删去,并定义一个变量记录剩余的人数,直接进行取模操作。非常节省时间。
idx = (idx+step)%mod;