【洛谷P1145】约瑟夫
问题描述
n 个人站成一圈,从某个人开始数数,每次数到 m 的人就被杀掉,然后下一个人重新开始数,直到最后只剩一个人。现在有一圈人,k个好人站在一起,k 个坏人站在一起。从第一个好人开始数数。你要确定一个最小的 m,使得在第一个好人被杀死前,k 个坏人先被杀死
输入格式
一行一个整数 k。
输出格式
一行一个整数 m。
样例输入
#1
3
#2
4
样例输出
#1
5
#2
30
数据范围
0<k<14。
题解
最朴素的算法就是一个一个数,用一个计数器记录当前报的数,再用一个指针指向当前报数的人,如果当前位置上的人已经出局,就把指针往后移,直到找到第一个没有出局的人。
报数是循环报的,即最后一个人报完后,下一个数由第一个人报,为了方便循环的实现,我们从0开始编号,这样每次只要把指针加一再对总人数取模,就可以实现循环报数
考虑优化
我们只需要最后留下的都是好人,并且我们已经知道好人在前,坏人在后,这个相对位置是不会变的,而每个人最初的序号可以忽略
假设最初大家围成一个大圆,每个人坐在一把椅子上,每次淘汰一个人就把那个人的椅子搬走,把围成的圆缩小,再对每个人重新编号,这样保证了每一次淘汰操作前所有人的编号是连续的
由于我们只要淘汰报m的人,我们可以以报m个数为一个循环,淘汰掉最后一个人。前面已经实现每一次淘汰前所有的人的编号是连续的,所以只要记录上一次淘汰的人的位置,把这个位置加上m再对当前总人数取模就可以直接算出当前循环要淘汰的人
1 #include <cstring> 2 #include <cstdio> 3 int main() 4 { 5 int n,m,k,t,cnt,ok,i,j,num; 6 scanf("%d",&k); 7 n=k*2; 8 for (m=k;;m++) 9 { 10 t=0; ok=1; 11 for (j=1;j<=k;j++) 12 { 13 num=n-j+1; 14 if ((t+m)%num<k) 15 { 16 ok=0; 17 break; 18 } 19 t=(t+m)%num; 20 } 21 if (ok) 22 { 23 printf("%d\n",m+1); 24 break; 25 } 26 } 27 return 0; 28 }