题目描述:
Joseph
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 2209 Accepted Submission(s): 1342
Suppose that there are k good guys and k bad guys. In the circle the first k are good guys and the last k bad guys. You have to determine such minimal m that all the bad guys will be executed before the first good guy.
3 4 0
5 30
题意:
这道题是典型的约瑟夫环应用的问题。题目说的是有k个好人和k个坏蛋围坐在圆桌旁边,好人的编号是前k个,而坏蛋的编号是紧接着好人的后k个,然后就是与约瑟夫环问题相似的情况了,从1开始报数,报至m的人出列,然后从下一个人开始继续从1开始报数,并按此规律循环报数下去。而题目要求确定最小的m值,使得前k个出列的总是这k个坏蛋,即是所有坏蛋总是在好人之前出列。
解析:
对于这种确定最小值的题目,我们可以先看一下k的取值范围,0 < k < 14,咋一看,觉得k值很小,但是呢,这道题却是需要一点数学技巧的。刚开始的时候可能会没有头绪,所以我们可以从第一次报数开始一次次的分析,从而寻找出一般规律。
为了方便起见,我们设编号从0开始,这样的话报到m-1的人出列(这样做不用特判最后一个数的情况),从而简化分析过程。
一开始的时候,所有人的按照编号顺序的序列为:
0,1,2,3...k-2,k-1...2k-2,2k-1
由于题目要求是所有坏蛋总是在好人之前出列,所以我们可以将好人圈定起来,如果出列的人在此范围之内,说明就与题意不符。
在这里,我们可以设start_num和end_num两个变量来记录好人编号的起止,所以一开始的时候可将这两数初始化为:
int start_num = 0,end_num = k-1;
而第一次出列的人的序号为(m-1)%n(n表示当前序列总人数),为了方便,我们可以设一个变量q来记录该次出列的人的序号,即
q = (m-1)%n。当第一个人出列之后,从该人的下一个人又是按照相同的方式报数,因此呢,下一次报数的过程的编号序列为:
q+1,q+2,q+3...2k - 1,0,1,2...q-1
而由于这一过程依旧是从0开始报数,那么该过程中的编号序列中每个人报数序列依旧为:
0,1,2,3....2k-3,2k-2
那么这样的话,报数序列与编号序列对应起来:
0 ---> q +1
1 ---> q + 2
...
2k-3 ---> q - 2
2k-2 ---> q - 1
那么依旧按照上述处理过程,报数报到m-1的人出列,后面的过程也是与这两次的一模一样,所以我们在第一次记录了好人编号的起止之后,在一次出列处理之后,好人的相对编号发生了改变,但是好人与好人之间的"相对顺序“还是不变的,因此我们在第二次处理出列的时候,可以将其看成n-1阶的约瑟夫环问题,那么出列的人报的数还是(m-1)%n(n表示当前序列总人数),但是好人的起始编号却发生了改变,如果我们要把第二次处理出列的过程看成n-1阶的约瑟夫环的问题的话,相当于把所有人的编号都减去(q+1),这样的话,大家的编号又是从0开始了,这是这时候人数减1了。
所以这时候好人起止编号更新为:
start_num = start_num - (q + 1)
end_num = end_num - (q + 1)
而q = (m-1)%n
证明:(x-a)%n = (x-a+n)%n
右边等于((x-a)%n+n%n)%n = (x-a)%n
等于左边,因此得证。
因此则有(考虑到可能最后的差小于0的情况,因此需加上总人数再取余):
start_num = (start_num - (m%n)+ n)% n
end_num = (end_num - (m%n)+ n)% n;
而再往后继续递推,会发现其实后面报数出列的过程与前面的过程是一模一样的,这是将问题的维数降低(即总人数减少)。因此我们可以利用一个for循环,不断更新出列的人的编号,好人的起止编号即可。
完整代码实现:
#include<cstdio>
#include<algorithm>
bool Judge(int k,int m){ //k表示好孩子截止的序号,m表示报到的号数
int start_num = 0,end_num = k-1;
int kill_num;
for(int i = 2*k;i>k;--i){
kill_num = (m-1)%i; //i表示当前序列人数
if(kill_num>=start_num&&kill_num<=end_num){
return false;
}
start_num = ((start_num-m)%i+i)%i;
end_num = ((end_num-m)%i+i)%i;
}
return true;
}
int main(){
int ans[14];
for(int i = 1;i<14;++i)
for(int j = 1;;++j){
if(Judge(i,j)){
ans[i] = j;
break;
}
}
int q;
while(scanf("%d",&q)==1&&q){
printf("%d\n",ans[q]);
}
return 0;
}
约瑟夫环是经典问题,要多回顾。
如有错误,还请指正,O(∩_∩)O谢谢