博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

“约瑟夫”问题及若干变种

Posted on 2010-10-24 00:25  桃子在路上  阅读(2596)  评论(0)    收藏  举报

                                                            “约瑟夫”问题及若干变种

 

例1、约瑟夫问题(Josephus)

[问题描述]

M只猴子要选大王,选举办法如下:所有猴子按1…M编号围坐一圈,从第1号开始按顺序1,2,…,N报数,凡报到N的猴子退出到圈外,再从下一个猴子开始继续1~ N报数,如此循环,直到圈内只剩下一只猴子时,这只猴子就是大王。

M和N由键盘输入,1≤N,M≤10000,打印出最后剩下的那只猴子的编号。

例如,输入8  3,输出:7。

 

[问题分析1]

这个例题是由古罗马著名史学家Josephus提出的问题演变而来的,所以通常称为Josephus(约瑟夫)问题。

在确定程序设计方法之前首先来考虑如何组织数据,由于要记录m只猴子的状态,可利用含m个元素的数组monkey来实现。利用元素下标代表猴子的编号,元素的值表示猴子的状态,用monkey[k]=1表示第k只猴子仍在圈中,monkey[k]=0则表示第k只猴子已经出圈。

程序采用模拟选举过程的方法,设变量count表示计数器,开始报数前将count置为0,设变量current表示当前报数的猴子编号,初始时也置为0,设变量out记录出圈猴子数,初始时也置为0。每次报数都把monkey[current]的值加到count上,这样做的好处是直接避开了已出圈的猴子(因为它们对应的monkey[current]值为0),当count=n时,就对当前报数的猴子作出圈处理,即:monkey[current]:=0,count:=0,out:=out+1。然后继续往下报数,直到圈中只剩一只猴子为止(即out=m-1)。参考程序如下:

program josephus1a {模拟法,用数组下标表示猴子的编号}

const maxm=10000;

var m,n,count,current,out,i:integer;

    monkey:array [1..maxm] of integer;

begin

     write('Input m,n:');

     readln(m,n);

     for i:=1 to m do monkey[i]:=1;

     out:=0; count:=0; current:=0;

     while out<m-1 do

       begin

          while count<n do

          begin

               if current<m then current:=current+1 else current:=1;

               count:=count+monkey[current];

          end;

          monkey[current]:=0; out:=out+1; count:=0

       end;

     for i:=1 to m do

       if monkey[i]=1 then writeln('The monkey king is no.',i);

     readln

end.

 

[运行结果]下划线表示输入

Input m,n:8 3

The monkey king is no.7     {时间:0秒}

Input m,n:10000 1987

The monkey king is no.8544  {时间:3秒}

 

[反    思] 时间复杂度很大O(M*N),对于极限数据会超时。这已经是优化过的程序,大家可以去看未做任何优化的程序josephus1b.pas,这个程序的时间复杂度为O(M*N*K),K是一个不确定的系数,对应着程序中的repeat循环花费的时间。空间复杂度为O(M)。

program josephus1b;{模拟法,用数组下标表示猴子的编号}

const maxm=10000;

var m,n,count,current,out,i:integer;

    monkey:array [1..maxm] of integer;

begin

     write('Input m,n:');

     readln(m,n);

     for i:=1 to m do monkey[i]:=1;

     out:=0; count:=1; current:=1;

     while out<m-1 do

       begin

          while count<n do

          begin

               repeat{寻找圈上的下一只猴子}

                     current:=current+1;

                     if current=m+1 then current:=1

               until monkey[current]=1;

               count:=count+1

          end;

          monkey[current]:=0; out:=out+1; count:=0

       end;

     for i:=1 to m do

         if monkey[i]=1 then writeln('The monkey king is no.',i);

     readln

end.

 

[问题分析2]

在组织数据时,也可以考虑只记录仍在圈中的猴子的情况。用一个线性表按编号由小到大依次记录圈中所有猴子的编号,每当有猴子出圈时,即从线性表中删除对应元素,表中元素减少一个。程序中用变量rest表示圈中剩余的猴子数,即线性表中元素的总数。参考程序如下:

program josephus2a;  {模拟法,用数组元素的值表示猴子的编号}

const maxm=10000;

var m,n,current,rest,i:integer;

    monkey:array [1..maxm] of integer;

begin

     write('Input m,n:');

     readln(m,n);

     for i:=1 to m do monkey[i]:=i;

     rest:=m; current:=1;

     while rest>1 do

       begin

          current:=(current + n - 1) mod rest;

          if current=0 then current:=rest;

          for i:=current to rest-1 do monkey[i]:=monkey[i+1];

          rest:=rest-1

       end;

  writeln('The monkey king is no.',monkey[1]);

  readln

end.

 

[运行结果]下划线表示输入

Input m,n:10000 1987

The monkey king is no.8544  {时间:0.8秒}

 

[反    思] 时间复杂度为O(M*K),但K远远小于N,是for循环花费的时间,速度教快。空间复杂度仍然为O(M)。

      也可以用monkey[j]存放第j个猴子的后继编号。当第j个出圈时,只要把monkey[j]的值赋给它的前驱,这样就自然跳过了j,以后再也不会访问到它了,但是时间上反而不如前面的方法。程序如下:

program josephus2b;  {模拟法,用数组元素的值表示后继猴子的编号}

const maxm=10000;

var m,n,current,rest,i:integer;

    monkey:array [1..maxm] of integer;

begin

     write('Input m,n:');

     readln(m,n);

     for i:=1 to m-1 do monkey[i]:=i+1;

     monkey[m]:=1;current:=m;

     rest:=m;

     while rest>1 do

       begin

          for i:=1 to n-1 do current:=monkey[current];

          monkey[current]:=monkey[monkey[current]];

          rest:=rest-1

       end;

     writeln('The monkey king is no.',monkey[current]);

     readln

end.

 

[问题分析3] 本题用单向循环链表做,模拟的更形象。时间复杂度为O(m*n),空间复杂度也是O(m),极限数据比方法2稍慢。程序如下:

program  josephus3;  {模拟法3,单向循环链表}

  TYPE

    point=^node;

    node=record

          data:longint;

          next:point

         end;

  VAR  m,n,s:longint;    {s为计数器}

       p,q,head:point;

  BEGIN

    readln(m,n);

    new(head);q:=head;head^.data:=1;

    for s:=2  to  m  do

      begin

new(p);

p^.data:=s;  {建一个结点,并且赋上数据域}

       q^.next:=p;

       q:=p;        {把p接到q的后面,再把p作为新的q}

      end;

    q^.next:=head;  {做成循环队列}

    s:=1;q:=head;   {从队头开始报数,计数器也设为1}

    repeat

      p:=q^.next;

      s:=s+1;

      if s mod n=0 then begin   {报到了n}

               q^.next:=p^.next;  {p出圈}

             dispose(p)

          end

                   else q:=p;

    until q^.next=q;

    writeln('The monkey king is no.',q^.data);

    readln

  END.

 

[运行结果]下划线表示输入

Input m,n:10000 9873

The monkey king is no.8195

 

[问题分析4]

能不能不通过模拟而直接求出第k次出圈的猴子的编号呢?这就是递推法的思想。用递推来求约瑟夫问题的方案,主要是找到m个猴子选大王过程与m-1个猴子选大王过程之间的关系。假如m=5,n=3,有5个猴子:

1

2

3

4

5

图1

 

 

 

 

 

 

  然后进行第一步选猴子,从第一个数起,3出圈,剩下4只猴子(图2,4被标上红色表示下一次从4数起):

1

2

 

4

5

图2

1

2

3

4

图3

 

 

 

 

 

 

接下来的选择过程与m=4,n=3(即四只猴子选大王,图3,从1数起)的情况非常相似。图2中的4相当于图3中的1,图2中的5相当于图3中的2……。以此类推,得到如下对应关系(左边一列是图2中的数字,右边一列是图3中的数字):

4←1 (注意:4-1=3 ,4=(1+3-1)mod 5 +1)

5←2  (             5=(2+3-1)mod 5 +1)

1←3  (             1=(3+3-1)mod 5 +1)

2←4  (             2=(4+3-1)mod 5 +1)

仔细研究一下这些对应关系,便不难发现,从1数到4要数2、3、4三个数,从2数到5要数3、4、5三个数,从3数到1要数4、5、1三个数,从4数到2要数5、1、2三个数——都是三个数,而这个“三”是由第一个对应关系确定的:delta=4-1=3。

现在假设我们知道m’=4,n=3的方案(a[i])为3、2、4、1,那么按照上面介绍的对应关系, 我们就可以推出m=5,n=3的方案为b[i]=(a[i]+delta-1)mod m +1),即:得到的新序列为1、5、2、4,再在这个序列前加上第一个出队的n(=3)号猴子,就得到3、1、5、2、4,而这就是m=5,n=3的出圈序列。

    现在推广到m、n的情况,设a(m,i)表示m个猴子选大王第i个出圈的猴子编号,则:

1、第一个出圈的猴子是a(m,1)=(n-1)mod m +1。注:一般情况下m>=n,则a(m,1)=n。但m<n时就需要调整了。

2、数第二遍的时候,从a(m,1)的下一个开始数起,即:从a(m,1)mod m+1数起,得到delta。

Delta = a(m,1) mod m + 1 – 1 = a(m,1) mod m。

3、剩下的出圈顺序可由递推关系得出:a(m,i)=(a(m-1,i) + delta - 1) mod m + 1,其中i>1。

 

参考程序如下:

program josephus4a;  {递推法1}

const maxm=10000;

var a:array[1..maxm] of longint;

    k, m,n,i,j:longint;

begin

     write('please input m,n: ');

     readln(m,n);

     a[1]:=1;

     for i:=2 to m do

         begin

              a[i]:=(n-1)mod i +1;

              k:=(a[i] mod i +1)-1;

              for j:=1 to i-1 do

                  a[j]:=(a[j]+k-1) mod i +1;

         end;

     writeln('The monkey king is no.',a[1]);

     readln

end.

 

[反    思] 这样的递推实际效果并不好,时间复杂度没有降下来。

           还可以这样考虑,设m个人从第s个人开始报数,报到n出圈,当剩余rest个人的时候,出圈者的编号应该为:(s+n-1) mod rest。举例来说,现在16个人,从第1个人开始报数,报到8出圈,则第1个出圈者的编号为:(1+8-1) mod 16 = 8,而继续报数,还是从第8个位置开始,所以第2个出圈者的编号就是:(8+8-1) mod 15 = 0,此时为特例(=0),出圈的人正好是最后一个编号,则出圈的人就是最后15。这个程序如下:

program josephus4b; {递推法2}

const maxn=10000;

var monkey:array[1..maxn] of longint;

    m,n,rest,out,i:longint;

begin

  write('input m,n:');

  readln(m,n);

  out:=1;

  for i:=1 to m do monkey[i]:=i;

  for rest:=m downto 2 do

    begin

      out:=(out+n-1) mod rest;

      if out=0 then out:=rest;

      for i:=out to rest-1 do monkey[i]:=monkey[i+1];

    end;

  writeln(monkey[1]);

  readln

end. 

 

[问题分析5] 以上两个递推的程序,之所以效率不好,是因为每次有一个元素出圈后,都要把后面的一些元素值进行调整。所以效率和模拟基本一样。可不可以不去调整呢?可以,这就是直接递推,方法就是[问题分析4]里的描述(红色字),时间复杂度为O(m),效率最好。

 

参考程序如下:

Program josephus5;  {直接递推}

var king,delta,n,m,i,j:longint;

begin

     readln(M,N);

     king:=1;

     for i:=2 to M do

         begin

              delta:=N mod i;

              king:=(king+delta-1) mod i +1;

         end;

     writeln('The monkey king is no.',king);

     readln

end.

 

[运行结果]下划线表示输入

Input m,n:10000 9873

The monkey king is no.8195  {时间:0秒}

 

例2、慈善的约瑟夫

[问题描述]

你一定听说过约瑟夫问题吧?!即从n个人中找出唯一的幸存者。现在老约瑟夫将组织一个皆大欢喜的新游戏,假设n个人站成一圈,从第1人开始交替的去掉游戏者,但只是暂时去掉(例如,首先去掉2),直到最后剩下唯一的幸存者为止。幸存者选出后,所有比幸存者号码高的人每人将得到1TK(一种货币),永久性的离开。其余剩下的人将重复以上的过程,比幸存者号码高的人每人将得到1TK后离开。一旦经过这样的过程后,人数不再减少,最后剩下的那些人将得到2TK。请你计算一下老约瑟夫一共要付出多少钱?

如图1,第一轮有5人,幸存者是3,所以4、5得到1TK后离开,下一轮幸存者仍然是3,因此没有人离开,所以每人得到2TK,总共要付出2+2*3=8TK。

 

[问题输入]

输入文件名:jose.in,输入文件包含一个整数,不超过32767。

 

[问题输出]

输出文件名:jose.out,输出文件包含一个整数,不超过65535,表示总共要付出的钱数(单位为TK)。

 

[样例输入与输出]

jose.in:

10

jose.out:

13

 

图1

 

[问题分析]

首先,很明显的一点是每个人都会得到1TK,只有最后的那些幸存者多得到了1TK,所以我们只要找出最后会幸存几个人便行了!假设经过m次后还剩final[m]个人,此时人数不再减少了,则问题的解应该为:final[m]+n。

那么,如何求final[m]呢?显然当第i次的final[i]=i时,人数就不会再减少了,此时的i即为m;否则,我们就需要对剩下的final[i]个人再进行报数出列操作。

设jose[i]表示i个人的圈报数后的幸存者编号,设报到k的人出去,则jose[i-1]可以理解为第一轮第一次报数,k出去后的状态。如下图左边的图(A),k出去后会从k+1继续报数,此时圈中有i-1个人,从k+1开始报数,编号如序列(1):

jose[i]:k+1,k+2,……,  i ,  1,2,……,k-1                   序列(1)

我们可以人为地把这个圈逆时针转k个单位,即变成右图(B)的状态,此时报数的序列如序列(2):

jose[i-1]:1, 2, ……, i-k , i-k+1,i-k+2,……,i-1           序列(2)

 

观察两个序列,我们发现,除了加边框的两个数据外,其它所有数据都满足下列规律:jose[i]=(jose[i-1]+k) mod i。

对于这个式子,稍做调整,变成公式(1),就都满足了。

jose[i]:=(jose[i-1]+1) mod i + 1                           公式(1)

至此我们就找到了问题的递推式,边界也很明显,jose[1]=1。然后我们顺推求出每个jose[i],直到某一次jose[i]=i,则final[i]:=i,否则final[i]:=final[jose[i]]。

 

[参考程序]

Program jsoe(input,output);

const max=32767;                                {假设最大顺推的次数}

var   jose:array[1..max] of integer;

      final:array[1..max] of integer;

      i,n:integer;

begin

  assign(input,'jose.in');reset(input);

  readln(n);close(input);

  jose[1]:=1;

  final[1]:=1;

  for i:=2 to max do                              {顺推}

    begin

      jose[i]:=(jose[i-1]+1) mod i + 1;          {递推求jose[i]}

if jose[i]=I                         {经过本次报数,编号比i大的都已出列,

then final[i]:=i                      所以本次幸存者的编号就是幸存人数}

  else final[i]:=final[jose[i]]    {对本次幸存者继续递推}

    end;

  assign(output,'jose.out');rewrite(output);

  write(final[n]+n);close(output)

end.

 

作 业

1、约瑟夫问题变形1

现在改成从第P个开始,每隔M只报数,报到的退出,直到剩下一只为止。最后剩下的为猴王。问:猴王是原来的第几只猴子?如:

Input m,n:10000 1987  34

The monkey king is no.8577

 

2、约瑟夫问题变形2

现在请你输出所有猴子出圈的顺序(其实最后一个就是猴王)。

如输入:8 3

输出:3 6 1 5 2 8 4 7

 

3、约瑟夫问题变形3

输入m,n,k。输出第k个出圈的是几号?

 

4、约瑟夫问题变形4

输入m,n,k。输出第k个猴子是第几个出圈的?

 

5、约瑟夫问题变形5

给定m和最后一个出圈者的编号,求最小的N?

[分析]

通过以上问题的解决,对于约瑟夫问题已比较熟悉了,首先我们按方法四中的递推算法继续思考,不难推出一般情况,即I 个人按J 报数的情况,当第一个人出圈以后,对报数的人进行重新编号,新编号为X 的人原编号应该为(X+J-1)mod I+1, 举例:新编号为1 的人的原编号应该为J+1, 但是J 有可能为I,这样一来J+1 就有可能越界,因此原编号应该为(1+J-1) mod I+1 。

按上面所讨论的情况,p[I] 表示I 个人按J 报数最后一个出圈者的编号。可以发现I 个人按J 报数与I-1 个人按J 报数最后出圈者之间的关系。即:p[I]=(p[I-1]+J-1) mod I+1, 初始条件为:p[1]=1 。

 

[参考程序]

Program ex5;

const max=1000;

var p:array[1..max] of integer;

n,m,i,k:integer;

begin

write('m&k');

readln(m,k);{ 输入总人数和最后留下的人的编号}

n:=0;

repeat

inc(n);

p[1]:=1;

for i:=2 to m do { 计算按n 报数时最后出圈者编号}

p[i]:=(p[i-1]+n-1) mod i+1;

until p[m]=k;{ 直到初圈的人为k}

writeln(n);{ 输出结果}

end.

 

6约瑟夫的新问题 

源程序文件名    jsf.pas

可执行文件名    jsf.exe

输入文件名      jsf.in

输出文件名      jsf.out

时间限制        1秒

问题描述

将1~M这M个自然数按由小到大的顺序沿顺时针方向围成一圈。以S为起点,先沿顺时针方向数到第N个数就出圈,然后再从刚出圈的那个数的左边沿逆时针方向数到第K个数再出圈,再从刚出圈的那个数的右边沿顺时针方向数到第N个数就出圈,然后再从刚出圈的那个数的左边沿逆时针方向数到第K个数再出圈,……。这样按顺时针方向和逆时针方向不断出圈,直到全部数都出圈为止。

请打印先后出圈的数的序列。

 

输入格式

文件中共4行,每行为一个自然数,分别表示M,S,N,K。M不超过1000。

 

输出格式

    仅1行,先后出圈的数的序列,每个数之间有1个空格。

 

样例输入(jsf.in)

8

1

3

2

样例输出(jsf.out)

3 1 5 2 7 4 6 8

 

样例解释

先从1开始沿顺时针方向数到3,所以3先出圈;再从2开始沿逆时针方向数到1,所以1出圈;再从2开始沿顺时针方向数到5,所以5出圈,再从4开始沿逆时针方向数到2,所以2出圈,……

 

[问题分析]

本题是对一般约瑟夫问题的扩展,只要稍加分析不难发现,本题可以套用一般约瑟夫问题的解法,无需作太大的改动就能完美地解决本题。而且本题的数据量较小(M≤1000),因此可以直接使用数组的方法来解决,速度也较快,能够符合问题的要求,如果问题的规模再扩大的话,就可能需要使用链表结构来解决才能符合要求。下面给出两种方法的参考程序。

 

[参考程序1]

对于具体实现,可以使用一个标志数组js来表示一个数的状态(在圈中还是已出圈),1表示这个数还在圈中,0表示这个数已出圈。每次从当前位置按照指定方向数,如果数到的数的标志数组元素是1,则应该记录,否则应该略过该数继续向下数,直到数到的圈中数的个数达到K或N,此时应输出当前的数并将其相应的标志数组元素置为0,如此反复,直到圈中所有的数均出圈为止。

程序中设一个变量total用于记录当前仍在圈中的数。过程cl作用为顺时针数n个数,并将数到的第n个数输出然后出圈。过程uncl用于逆时针数k个数,并将数到的第k个数输出然后出圈。程序首先将所有数的状态均为在圈中,然后不断按顺时针数一次(cl)、逆时针数一次(uncl)的顺序从圈中删数,直到圈中没有数为止。

 

Program Jsf1;  {数组模拟}

Var m,n,k,s,i,total:integer;

js:array[1..1000]of 0..1;

 

Procedure cl(var start:integer);                    {顺时针数数出圈}

var sum:integer;

begin

  sum:=0;

  while sum<n do                                    {当数到第n个数就出圈}

    begin

      if js[start]=0 then start:=start mod m+1      {跳过已出圈的数}

                     else begin

                            inc(sum);

                            start:=start mod m+1

                          end

    end;

  if total=m then write((start+m-2) mod m+1)        {输出出圈的数}

             else write(' ',(start+m-2) mod m+1);

  js[(start+m-2) mod m+1]:=0;                       {将出圈数作上出圈标记}

  start:=(start+m-3) mod m+1                        {计算下一次数数的起点}

end;

 

Procedure uncl(var start:integer);                  {逆时针数数出圈}

var sum:integer;

begin

  sum:=0;

  while sum<k do                                    {当数到第k个数出圈}

    begin

      if js[start]=0 then start:=(start+m-2) mod m+1 {跳过已出圈的数}

                     else begin

                            inc(sum);

                            start:=(start+m-2) mod m+1

                          end

    end;

  if total=m then write(start mod m+1)              {输出出圈的数}

             else write(' ',start mod m+1);

  js[start mod m+1]:=0;                             {将出圈数作上出圈标记}

  start:=(start mod m+1) mod m+1                    {计算下一次数数的起点}

end;

 

Begin  {main}

  assign(input,'jsf.in');  reset(input);

  assign(output,'jsf.out');rewrite(output);

  readln(m);readln(s);readln(n);readln(k);

  total:=m;                                         {统计圈中的数的个数}

  for i:=1 to m do js[i]:=1;                        {开始将各数设为在圈中}

  while total<>0 do                                 {从圈中删数}

    begin

      cl(s);                                        {从当前起点顺时针数n个数}

      dec(total);                                   {圈中的数减少一个}

      if total<>0 then begin

                         uncl(s);                   {从当前起点逆时针数k个数}

                         dec(total)                  {圈中的数减少一个}

                       end

    end;

  close(input);close(output)

End.

 

[参考程序2]

program jsf2;  {双向循环链表模拟}

type

      pnode=^node;                           {双向循环链表的指针类型}

          node=record                        {双向循环链表的结点类型}

            id:integer;                      {自然数}

            next:array[0..1] of pnode;       {顺时针方向和逆时针方向的后继指针}

          end;

var

    root ,tail,start:pnode;     {双向循环链表的队首指针和队尾指针,起点数的结点指针}

    n,i,s:integer;              {n—连续自然数的个数,s—由s开始报数}

    mk:array[0..1] of integer;  {mk[0]—顺时针的步长值,mk[1]—逆时针的步长值}

 

procedure add_node(id:integer);      {该过程将自然数id加入双向循环链表,

var   p:pnode;           通过for i:=n downto 1 do add_node(i);便可构造双向循环链表}

begin

    new(p);                           {为自然数id 申请内存}

    p^.id:=id;                        {p结点加入双向循环链表}

    p^.next[1]:=root;

    p^.next[0]:=tail;

    if root<>nil

      then begin                       {设定双向循环链表的队首指针和队尾指针}

             root^.next[0]:=p;

             tail^.next[1]:=p

           end

      else root:=p;

   tail:=p

end;

 

function get_dulist(s:integer;la:pnode):pnode; {计算初始结点,由于报数从s开始,

var p:pnode;   因此首先必须在以la为首结点的双向循环链表中计算出第s个结点的地址}

j:integer;

begin

    p:=la;j:=1;

    while (p^.next[0]<>la)and(j<s) do begin p:=p^.next[0];j:=j+1 end;

    if j=s then get_dulist:=p else get_dulist:=nil

 end;

 

function delete(flag:integer;node:pnode):pnode;

{队员出列,设当前开始报数的结点为node,报数的方向为flag(=0时顺时针,=1时逆时针)。

函数delete(flag, node)完成如何删去下一个数,并返回其flag方向相邻的结点指针}

var

    p:pnode;

    i:integer;

begin

    p:=node;                                   {沿flag方向计算下一个出列数的结点p}

    for i:=1 to mk[flag] do p:=p^.next[flag];

    p^.next[0]^.next[1]:=p^.next[1];           {从双向循环链表中删去结点p }

    p^.next[1]^.next[0]:=p^.next[0];

    delete:=p^.next[flag];                     {返回p在flag方向相邻的结点指针}

    write(p^.id,' ');                          {输出出列数}

    dispose(p);                                {释放结点p }

end;

 

{ 我们通过调用n次delete函数,便可以完成出列顺序的计算。第一次调用时,方向数设为0,开始报数的结点设为start。以后每一次调用方向数取反,指针指向当前开始报数的结点。这样依次类推,直至所有数出列为止。}

 

begin  {main}

    assign(input,'jsf.in');

    assign(output,'jsf.out');

    reset(input);

    rewrite(output);

    readln(n);                                          {读自然数的个数}

    readln(s);                                          {读起点数}

    readln(mk[0]);                                      {读顺时针的步长值}

    readln(mk[1]);                                      {读逆时针的步长值}

    for i:=n downto 1 do add_node(i);                   {构造出双向循环链表}

    start:=get_dulist(s,root);                          {计算起点数的结点指针}

    i:=0;                                               {由顺时针开始报数}

    while n>1 do                                        {若剩余数大于1,则循环}

      begin

        start:=delete(i,start);

{从start 结点出发,沿方向i删去下一个数,并返回其i方向相邻的结点指针}

        i:=1-i;n:=n-1;                                  {方向数取反,剩余数-1}

      end;

    writeln(start^.id);                                 {输出最后一个出列数}

    {dispose(root);}                                    {释放该结点}

    close(input);

    close(output);

end.