POJ 1012 Joseph 变形约瑟夫环

56 篇文章 0 订阅
33 篇文章 0 订阅

子问题与原问题........

题意:

有k个坏人k个好人坐成一圈,前k个为好人(编号1~k),后k个为坏人(编号k+1~2k)

现在有一个报数m,从编号为1的人开始报数,报到m的人就要自动死去。问当m为什么值时,可以使得在出现好人死亡之前,k个坏人先全部死掉?
PS:当前一轮第m个人死去后,下一轮的编号为1的人 为 前一轮编号为m+1的人。 前一轮恰好是最后一个人死掉,则下一轮循环回到开头那个人报“1”


这道题起初看以为是道模拟题,因为数据结构课上写过。但是必然超时····到11的时候就卡死了。于是乎找到题解打表过了~~~~

搜资料后发现,运用重新标号,子问题求解。

最初的约瑟夫:将问题变为有n个人 编号为0~n-1,从0开始报数,到m-1时,此人出列。再从下一个开始。求胜利者的编号。

/**
    问题描述:已知n个人(编号1,2...n)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;
               他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,求最后出列的人的编号。
               (以下代码是默认k = 1)

    (1) 循环链表的方法时间复杂度为O(n*m).
    (2) 一个时间复杂度为O(n)的数学方法,思路如下:
           假设当前剩下i个人(i<=n),显然这一轮m要挂(因为总是从1开始数).经过这一轮,
           剩下的人是:1 2 3...m-1 m+1...i, 我们将从m+1开始的数映射成1,则m+2对应2, n对应i-m,
           1对应成i-m+1, m-1对应i-1,那么现在的问题变成了已知 i-1个人进行循环报数m,求出去的人的序号。
           假设已经求出了i-1个人循环报数下最后一个出去的人的序号x0,那么它在n个人中的序号 x1 = (x0+m-1)%n + 1,
           最初的 x0=1 ,反复迭代 x0和x1可以求出.
**/


#include <stdio.h>

int main()
{
    int n, m, s, i;

    scanf("%d %d", &n, &m);
    s = 1; //初值为1
    for (i=2; i<=n; i++)
    {
        s = (s+m)%i;
        if (s == 0) s = i;
          //这两个语句也可以用s = (s+m-1)%i + 1;来代替
    }
    printf("answer = %d/n", s);
}


POJ 1012 

ans[i] 对应第i次出队人的编号 ans[0]=0;
ans[i]=(ans[i-1]+m-1)%(n-i+1);   (i>1  ,  总人数n=2k 则n-i为第i轮剩余的人数)

优化:

看到很多人对m并不是递增+1,而是m+=k+1  或m+=k+2。 为什么?考虑当最后剩下K+1个人。此时必然K+1为终止点,但是起始位置要看上一轮K+2。K+2可能在K+1的左侧,那么m必然是t*(k+1+1),也可能在右侧,m=t*(k+1),

接下来说说m的取值范围:我们考察一下只剩下k+1个人时候情况,即坏人还有一个未被处决,那么在这一轮中结束位置必定在最后一个坏人,那么开始位置在哪呢?这就需要找K+2个人的结束位置,然而K+2个人的结束位置必定是第K+2个人或者第K+1个人,这样就出现两种顺序情况:GGGG.....GGGXB 或  GGGG......GGGBX (X表示有K+2个人的那一轮退出的人)所以有K+1个人的那一轮的开始位置有两种可能即第一个位置或K+1的那个位置,限定m有两种可能:t(k+1) 或 t(k+1)+1; t>=1; 若遍历每一个m必定超时,避免超时则需要打表和限制m的范围。

#include<stdio.h>
int a[14];
int f(int k,int m)
{
    int n,i,s;
    n=2*k;s=0;
    for(i=0;i<k;i++)
    {
        s=(s+m-1)%(n-i);
        if(s<k) return 0;//遇到前k轮中有小于k的直接返回0
    }
    return 1;
}
int main()
{
    int i,k,n;
    for(k=1;k<=14;k++)
    {
        i=k+1;
        while(1)
        {
            if(f(k,i))//t(k+1)的情况
            {
                a[k]=i;
                break;
            }
            else if(f(k,i+1))//t(k+1)+1的情况
            {
                a[k]=i+1;
                break;
            }
            i+=k+1;
        }
    }
    while(scanf("%d",&n) && n)
    {
        printf("%d\n",a[n]);
    }
    return 0;
}


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值