ZOJ 1088 System Overload

来源:互联网 发布:2016年母婴数据分析 编辑:程序博客网 时间:2024/04/26 09:03

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=88

题意:有几幢楼分别从1~n编号,现在1号楼首先断网,从下一幢未断网的楼从1开始数(数的是未断网的楼)到m,对数到m的楼进行断网,求当有n幢楼时,m是多少2号楼是最后断网的。

例:当n=10时,m=3,2号楼是最后断网的,断网顺序:1;4;7;10;5;9;6;3;2

解题思路:

有两种做法,一种是暴力的进行模拟,时间效率不高,另一种是用数学的方法做,也就是约瑟夫环问题,

//模拟做法

//Judge StatusProblem IDLanguageRun Time(ms)Run Memory(KB)//Accepted  1088       C++         520          272#include <iostream>#include<cstdio>#include<cstring>using namespace std;int main(){    int n,i;    bool cut[155];    while(scanf("%d",&n) && n!=0)    {        bool find=false;//表示是否成功        int m=1;//m从1开始模拟        while(!find)        {            memset(cut,true,sizeof(cut));//每次模拟对所有楼设为未断网             cut[1]=false;//1号楼首先断网            int before=1;//记录前一幢断网的楼号            int k=m;//k用于辅助            int num=n-1;//表示未断网的楼的数量            while(num!=1)//当未断网的楼只有一幢时此次m值的模拟结束,判断未断网的楼是否为2号来判断            {            //这个m值是否成功                for(i=1;i<=k;i++ )                    if(!cut[(before+i)%n])//在数的过程中遇到以断网的楼那么k的值需要加1,来跳过这幢楼                          k++;//对于%n,因为是循环                before=(before+k)%n;//记录前一幢断网的楼                cut[before]=false;//把那幢楼状态设为断网                   num--;//未断网楼数减1                if(cut[2]==false)//如果在模拟过程中遇到2号楼断网了那么这个m是错误的直接进行下一次模拟                    break;                    k=m;//在模拟中为了跳过以断网的楼k值也许已经改变,但是再数下一幢时k要变成m再数            }            if(cut[2]==true && num==1)//判断成功的条件                find=true;            else                m++;        }        printf("%d\n",m);    }    return 0;}

约瑟夫环:

首先明确约瑟夫环问题:约瑟夫环是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。

 

好了下面开始推公式:

给出一个序列,从0~n-1编号。其中,k代表出列的序号的下一个,即k-1出列。

a        0, 1, …, k-1, k, k+1, …, n-1

k-1出列后得到

b      0, 1…k-2,.k, k+1 ,….., n-1

对b进行转化一下,把0~k-2这串数列挪到k~n-1后面得到

b*    k ,k+1 ,….n-1, 0, 1,….k-2

对b*数列进行一种映射:

k      -----> 0 
           k+1    ------> 1 
           k+2    ------> 2 

…..
           n-1   ------>n-k-1

0             ------>n-k

1             ------>n-k+1

     ...
                ... 

          k-2   ------>  n-2 

也就形成c数列

C   0 ,1, 2,…., n-k-1, n-k, n-k+1, …….n-2

可以看出右边的值加个k对其取余(%n)就是左边的的值

 这是一个n -1个人的问题,如果能从n -1个人问题的解推出 n 个人问题的解,从而得到一个递推公式,那么问题就解决了。假如我们已经知道了n -1个人时,最后胜利者的编号为x,那么他原本的编号就是(x+k)%n,也就是胜利者的编号。

其中k等于m % n。代入(x + k) % n  <=>  (x + (m % n))%n <=> (x%n +(m%n)%n)%n <=> (x%n+m%n)%n <=> (x+m)%n

 

假设第三轮的开始数字为B,那么这n - 2个数构成的约瑟夫环为B, B + 1, B + 2,......B - 3, B - 2.。继续做映射。

            B         ----->  0 
             B+1    ------>1 
             B+2    ------>2 
               ... 
               ... 

            B-2     ------>  n-3 

         这是一个n - 2个人的问题。假设最后的胜利者为y,那么n -1个人时,胜利者为 (y + B) % (n -1 ),其中B等于m % (n -1 )。代入可得 (y+m) %(n-1)

         要得到n - 1个人问题的解,只需得到n- 2个人问题的解,倒推下去。只有一个人时,胜利者就是编号0。下面给出递推式:

         f [1] = 0; 
          f [ i ] = ( f [i -1] + m) % i;(i>1) 

也许你会迷惑为什么%n变成公式中f[i]=(f[i-1]+m)%i中的%i?其实这个稍微想想就能明了。我们%n就是为了从序列c转换到序列b*——这是在n-1序列转换成n序列时%n;那么从n-2转换到n-1呢?不是要%(n-1)了吗?所以这个值是变量,不是常量。

好了公式推导完毕,下面回到这道题,依旧以n=10,m=3为例子,对他的映射转换过程具体分析。

首先1号楼直接断网,排除1号楼,映射关系从2号楼开始

红色数字表示2号楼在映射过程编号的变化,黄色数字表示下个断网楼号映射后的编号

第一次映射

     2 - 0

     3 - 1

     4 - 2

     5 - 3

     6 - 4

     7 - 5

     8 - 6

   9 - 7

   10 - 8

第二次映射

     3   0

     4  1

     5  2

     6  3

     7  4

     8  5

     0  6

     1  7

 

 

第三次映射

      3   0

     4   1

     5   2

     6   3

     7  4

     0  5

     1  6

第四次映射

    3  0

    4  1

    5  2

    6  3

   0  4

  1  5

第五次映射

   3  0

   4  1

   5  2

   0  3

   1  4

第六次映射

   3  0

   4  1

   0  2

   1  3

第七次映射

   3  0

   0  1

   1  2

第八次映射

   0  0

   1  1

第九次映射

  1  0

 

最后总结:

2号楼最后映射关系后的编号肯定为0,因为只有他这么一幢楼,然后就是由后往前推,9次推后(n-1)它是否依旧是0

//公式//Judge StatusProblem IDLanguageRun Time(ms)Run Memory(KB)//Accepted       1088      C++         0            272#include<iostream>#include<cstdio>#include<cstring>using namespace std;int n;int judge(int m){    int f=0;    //由于开始XWBy一号大楼开始直接出队,所以从2~n进行编号0~n-2    for(int i=2;i<n;i++)        f=(f+m)%i;    if(f==0)//编号为0,对应2号大楼        return 1;    else        return 0;}int main(){    while(scanf("%d",&n) && n!=0)    {        int m=1;        while(!judge(m))            m++;        printf("%d\n",m);    }    return 0;}




0 0
原创粉丝点击