目录
背景:
作为一名刚转专业到软件工程的菜鸡小白,只是简单的知道一些基础的c语言的编程语言,第一次听到ACM,然后抱着试一试的求虐心态就参加了校内举办的ACM的选拔赛,3个小时!!一共九道题,我就做出了一道!!没接触过算法的我,决定开始我的算法学习之路!!!
今天整理的是大紫本上面的一道函数题,感觉特别的奇妙,题目如下:
题目:
n(n<20)个人站成一圈,逆时针编号为1~n。 有两个官员,A从1开始逆时针数,B从n开始顺时针数。 在每一轮中,官员A数k个就停下来,官员B数m个就停下来(注意有可能两个官员停在同一个人上)。 接下来被官员选中的人(1个或者2个)离开队伍。输入n,k,m输出每轮里被选中的人的编号(如果有两个人,先输出被A选中的)。 例如,n=10,k=4,m=3,输出为4 8, 9 5, 3 1, 2 6, 10, 7。 注意:输出的每个数应当恰好占3列。
心路历程:
看到这个题,刚刚学完链表的我首先想到的肯定是用循环链表来解决这种类似于敢死队问题的环状问题在用这种方法写完代码后和大紫书一对,***他竟然用数组就解决了,在学循环链表的时候我也不是没有想到这种用数组来解决链表问题的大好方法,可是当时想了半天,算了我还是乖乖用链表吧!!下面让我们来看看大紫书的作者是如何实现用数组来循环的?
解法剖析:
首先,我们来画个图:
算法1:链表,思路简单但是十分繁杂动不动100行代码,浪费我选冰冰的宝贵时间。
算法2:求余,这个好这个简单。
逆时针顺时针的实现:主函数传入q,q=1则为逆时针选,q=-1则为顺时针选。
走多少步数k的实现:主函数传入步数t,while(t--)
重点1:实现选中冰冰的离开问题:如果冰冰被选中离开,则标记冰冰为零,如果这个位置的冰冰标记为0;则向下走一步用循环来实现如下:
do {
//最重要的实现数组循环的语句;
}
while(a[p] == 0);
这样就解决了被选中的冰冰跳过问题
重点2(本题之眼):如何实现数组的循环选择?答案是求余!!
如果为p=(p+q+n)%n我们可以发现,看上图冰冰图,你选择(顺时针),如果你选择到了一号冰冰后想要选择八号冰冰的时候,我们把p=2带入得p=1没有问题。可是!p=1带入得p=(1-1+8)%8=1选不到8号冰冰了!
Ps.因为一个数n求余得到的余数为0<=n的余数<n的,这个时候我们就要从里面减去1再在外面+1从而使得这个数商0而余数就可以取到自身了范围变成了0<n的余数<=n,神奇吧!!!当时我就觉得这里最神奇了
<为了避免这种情况:我们要使用p=(p+q+n-1)%n+1来这个时候我们再带入就发现p=8了,实现了循环的选择
这时候又有小朋友问,那么如果p=2的时候不就等于0了嘛,不不不我们带进去可以发现p=(2-1-1+8)%8+1=1,因为我们的范围变成了0<n的余数<=n
所以选老婆函数为:
int go(int p, int q, int t){
while(t--)
{
do {
p = (p + q + n-1)%n + 1;
}
while(a[p] == 0);
}
return p;
}
这个函数写出来了下面的问题就迎刃而解了!!
而这个选老婆问题(bushi)的总代码为:
#include <stdio.h>
#define maxn 25
int n, a[maxn];
int go(int p, int q, int t){
while(t--)
{
do {
p = (p + q + n-1)%n + 1;
}
while(a[p] == 0);
}
return p;
}
int main()
{
int k, m, p1, p2, left;
while(scanf("%d %d %d", &n, &k, &m) == 3 && n)
{
left = n, p1 = n, p2 = 1;
for(int i = 1; i <= n; i++) a[i] = i;
while(left) {
p1 = go(p1, 1, k);
p2 = go(p2, -1, m);
printf("%d",p1);
left--;
a[p1] = 0;
if(p1 != p2)
{
printf(" %d", p2);
left--;
a[p2] = 0;
}
if(left) printf(",");
}
printf("\n");
}
return 0;
}
总结:
1.利用数学知识p=(p(所在地)+q(步)+n(总人数))%n就可实现数组的循环实现问题结合标记量为0,便可实现代替循环链表的问题。巧妙!
2.灵活使用全局变量可以避免函数中,值的传递要改变主函数值时的要使用到指针的问题
3.冰冰真好看。