n n n 个人站成一圈,逆时针编号为 1 ∼ n 1\sim n 1∼n。有两个官员, A \text{A} A从 1 1 1 开始逆时针数, B \text{B} B 从 n n n 开始顺时针数。
在每一轮中,官员 A \text{A} A 数 k k k个就停下来,官员 B \text{B} B 数 m m m 个就停下来(两个官员有可能能停在同一个人上)。接下来被官员选中的 1 1 1 个或 2 2 2个人离开队伍。
输出离开队伍的顺序,如果有两个人,先输出 A 选中的。
本题多测。
对于每组数据,输入共一行,输入 n , k , m n,k,m n,k,m。
数据以 0 0 0 \texttt{0 0 0} 0 0 0 结尾。
对于每组数据,输出一行,为离开队伍的顺序。
输出的每个数应正好占 3 3 3 列。
样例中的“ ␣ ”代表一个空格。
10 4 3
0 0 0
␣␣4␣␣8,␣␣9␣␣5,␣␣3␣␣1,␣␣2␣␣6,␣10,␣␣7
对于 100 % 100\% 100% 的数据,满足 0 < n < 20 0
使用数组或列表模拟队列
使用一个数组/向量来保存还留在队列中的人。
每次在当前列表中找到要被淘汰的人的位置:
第一次顺时针找第 k 个;
第二次逆时针找第 m 个(注意是反方向);
删除该人并记录其编号;
直到队列为空为止。
如何高效地处理“环”结构?可以用取模 % 来实现循环查找;
注意删除元素后,索引的变化;
注意方向变化(正向 vs 反向)。
int go(int p, int d, int t)//寻找下一个选中的人
{
while (t--)
{
do {
p = (p + d + n - 1) % n + 1;
} while (arr[p] == 0);
}
return p;
}
上面的代码是解决约瑟夫问题的关键
p:代表当前位置
d是代表移动的方向,1代表逆时针移动,-1代表顺时针移动,
t:要移动的步数(即第几个有效的人)
以10 4 3为例
初始A在第一个位置,需要数4次,分别是1,2,3,4
所以设置A的初始位置p=0,移动方向d = 1,移动次数 t = k = 4
初始B在第十个位置,需要数3次,分别是10,9,8
所以设置B的初始位置p = 11,移动方向 d = -1,移动次数 t = m =3
如果查找下一个位置没有人,也就是arr[p]==0,那就继续找下一个
p = (p + d + n - 1)%n + 1
这里加 n-1 的作用就是实现环形数组的循环,为什么不是+n而是n-1呢
因为%模运算后的结果在[ 0 , n-1 ],但是原数组的下标是 [ 1 , n ],也就是说原本的数组范围要先映射到模运算的数组范围,最后再还原成原数组下标,也就是+1 的原因。
#include
using namespace std;
int n, k, m;
int arr[25];
int go(int p, int d, int t)
{
while (t--)
{
do
{
p = (p + d + n - 1) % n + 1;
} while (arr[p] == 0);
}
return p;
}
int main()
{
while (cin >> n >> k >> m)
{
if (!n && !k && !m)
break;
for (int i = 1; i <= n; i++)
arr[i] = i;
int res = n;
int p1 = 0, p2 = n + 1;
while (res)
{
p1 = go(p1, 1, k);
p2 = go(p2, -1, m);
arr[p1] = 0;
res--;
printf("%3d", p1);
if (p2 != p1)
{
res--;
arr[p2] = 0;
printf("%3d", p2);
}
if (res)
printf(",");
}
cout << endl;
}
return 0;
}