UVA133 救济金发放
题目描述
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 列。
样例中的“ ␣ ”代表一个空格。
输入输出样例 #1
输入 #1
10 4 3
0 0 0
输出 #1
␣␣4␣␣8,␣␣9␣␣5,␣␣3␣␣1,␣␣2␣␣6,␣10,␣␣7
说明/提示
数据范围
对于 100 % 100\% 100% 的数据,满足 0 < n < 20 0<n<20 0<n<20, 0 < k , m 0<k,m 0<k,m。
方法一:
使用数组或列表模拟队列
使用一个数组/向量来保存还留在队列中的人。
每次在当前列表中找到要被淘汰的人的位置:
第一次顺时针找第 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 <bits/stdc++.h>
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;
}