ZOJ 1088 System Overload

题目链接: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 Status	Problem ID	Language	Run 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 Status	Problem ID	Language	Run 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值