参考:
https://matiji.net/exam/brushquestion/19/3181/1DC60EA6DF83A333301CFFE1407FBA59
https://www.bilibili.com/video/BV1ss4y1Y7gB/?t=1065.1

问题引入

马上要进行阅兵仪式了,将军要在手下 nn 个人中挑人参加,他用以下方法选人:

nn 个人站成一圈,逆时针编号为 1n1-n ,现在 ABA、B 两人选人。AA 逆时针数 kk 个停下来,BB 顺时针数 mm 个停下来,被选中的1或2个人输出,直至所有人输出。

首次从 AA 从1开始, BBNN 开始,之后 ABAB 分别从上次各自的位置的各自方向的下一位接着数。结合样例1可知:

请自行推理



输入格式:
可能有多组数据,每组一行输入三个整数n、k、m,输入0 0 0 停止。
输出格式:
输出每轮选出的人,用,隔开,每个编号占3个格子(右对齐)。



输入:
10 4 3
0 0 0
输出:
 4 8, 9 5, 3 1, 2 6, 10, 7



备注:
0<n200<n≤20;k、m在int范围内,且为正数;组数少于20。


问题分析

圈圈编号循环问题。我采用的是双循环的办法,就是直接将题目翻译一遍,比较直白。但是编号的办法太笨了。写了80多行代码才ac。

题解代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include<bits/stdc++.h> 
using namespace std;

int ans(int nn[30],int n,int k,int m);

int main( )
{
int n,k,m;
cin >> n >> k >> m;
while(n!=0 && k!=0 && m!=0)
{
int nn[30]={0};
ans(nn,n,k,m);
cin >> n >> k >> m;
}

return 0;
}

int ans(int nn[30],int n,int k,int m)
{
int a=0,b=1,total=n;
int aa=0,bb=0;
for(int i=1;i<=n;++i)
{
nn[i]=1;
}
while(total>0)
{
while(aa<k)
{
a=(a+1)%(n+1);
if(nn[a]==1)
{
aa++;
}
}
aa=0;
while(bb<m)
{
b=(b-1)%(-n);
if(nn[n+b]==1)
{
bb++;
}
}
bb=0;


if(a==n+b)
{
total--;

if(total==0)
{
printf("%3d\n",a);
}
else
{
printf("%3d,",a);
}
}
else
{
total-=2;
if(total==0)
{
printf("%3d%3d\n",a,n+b);
}
else
{
printf("%3d%3d,",a,n+b);
}

}
nn[a]=0;
nn[n+b]=0;
}
return 0;
}

改进

看了up主轩哥码题的解题思路,学到了一个重要知识点:

通常用静态数组加下标的方式模拟这种循环结构。

任意的起始点到任意的终点,用下标进行循环。
假设数组 A ,起始位置为 k ,当前的位置为 p ,每一次的步长为 d (其中 d 有两种可能,1和-1 。1表示正循环,-1表示逆循环),循环体周期的长度为 T 。
由此得到下标的循环公式:

(pk+d+T)%T+k(p-k+d+T)\%T+k

给上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<bits/stdc++.h> 
using namespace std;

int n,k,m;
bool a[25];

//direction为1为逆时针,为-1为顺时针
//position,direction,count是总步长
int pick(int p,int d,int c)
{
while(c--)
{
do{
p=(p-1+d+n)%n+1;
}while(a[p]==0); //a[p]为0表示已经访问过p坐标了
}
return p;
}

int main()
{
while(cin >> n >> k >> m,!(n==0 && k==0 && m==0))
{
for(int i=1;i<=n;++i)
{
a[i]=true;
}
int remain=n; //剩余人数
int pa=n,pb=1; //pa是从1往前走了一位,pb是从n往后走了一位
while(remain)
{
pa=pick(pa,1,k);
pb=pick(pb,-1,m);
if(pa==pb)
{
printf("%3d",pa);
remain--;
}
else
{
printf("%3d%3d",pa,pb);
remain-=2;
}
a[pa]=a[pb]=0;

if(remain)
printf(","); //这是比较巧妙的输出方式
}
printf("\n");
}
return 0;
}

OvO