cqyz#递归-p3531 约瑟夫问题【6】

【问题描述】
  YJC 很喜欢玩游戏,今天他决定和朋友们玩约瑟夫游戏。

  约瑟夫游戏的规则是这样的:n个人围成一圈,从1 号开始依次报数,当报到m 时,报1、2、…、m-1 的人出局,下一个人接着从1 开始报,保证(n-1)是(m-1)的倍数。最后剩的一个人获胜。

  YJC 很想赢得游戏,但他太笨了,他想让你帮他算出自己应该站在哪个位置上。

【输入格式】
  第一行包含两个整数n 和m,表示人数与数出的人数。

【输出格式】
  输出一行,包含一个整数,表示站在几号位置上能获得胜利。

【输入样例】
10 10

【输出样例】
10

【数据范围】
对于30%的数据,2 ≤ n ≤ 1000。
对于70%的数据,2 ≤ n ≤ 1000000。
对于100%的数据,2 ≤ m ≤ n ≤ 2^63-1

【来源】
八中供题

【原题传送矩阵】
p3531 约瑟夫问题【6】

【思路梳理】
  “看待一个问题不能仰视之,要居高临下地审视之。”——Mr. He
  这句话是我校竞赛教练 Mr.He的原话,意指对于任何一个题不应该只想着模拟,而应该结合自己的所学知识,跳出思维的怪圈,真正做一个ACMer。

  这句话用于诠释此题不能再恰当了。拿到问题的第一瞬间,不才我是一头雾水。事后才在高人的指点下琢磨出了100分算法。

  看数据规模,30分模拟,70分似乎应该是考虑贪心或者动态规划,而100分则达到了2^61 -1,不可能通过存储实现,应该是使用递归调用,将结果作为参数或者返回值传递后输出。
算法1:30分模拟
  按照题目描述地模拟,将报1~m-1的人标记掉,直到只剩1个人,输出之。

算法2: 70分递推
  这一下就有意思了。知道是递推,不难想到设置状态函数f[i]=在前i个人中进行约瑟夫游戏剩下的人的编号。但是转移呢?在每m个人中,报1~m-1的人都会出局,而只剩下了第m个人。所以每增加m-1个人,答案往后移m位。所以递推式:f[i]=f[i-m+1]%(i-m+1)+m,边界f[1]=1.时间复杂度这里写图片描述。

算法3:100分递归
  推公式找规律。问什么设什么,设置一个函数solve(x),表示在这x个人中进行约瑟夫游戏时最后一个人的编号。下面思考递归。
  当前有x个人的话,那么这x个人可以分成两部分:

  这里写图片描述

前面的一个部分可以被均分为了a组,每一组都有m个数;后面的一个Part有任意个,但是小于m。前面的Part1里面的人数显然是m的a倍,那么未出局的就是编号为m的整数倍的人;后面的Part2是不足再报m个数的人,总共数量是n%m。
  我们继续对这些剩下的人重新编号(因为还有人没有报数,所以新的编号要从当前编号为am+1的人开始),将后面的n%m个人拉到新队伍的前面来。而前面总共是n/m组,那么此时还未出局的总人数为Part1的n/m个人和Part2的n%m个人。我们继续在这些人里重新进行约瑟夫游戏。
  什么时候停止呢?显然最后肯定会有一个人留下来(n-1是m-1的整数倍),所以边界是solve(1)=1。
  此时递归已经开始回溯,返回值是这一轮进行以后留下来的人的标号。我们要求出他在上一轮进行后留下来的编号,只需要找到他的位置即可。当前来说,因为后面的人数都小于m,那么也就是说这些人会全部出局(留下来的人在Part1),而且一定是m的整数倍(非整数倍都出局了)。设返回值为y,则我们可以知道他的编号为:(y-n%m)*m。
  

#include<Cstdio>
#include<iostream>
#define maxn 1000005
using namespace std;
long long n,m;
long long f[maxn];

void solve30()
{
    int count=1,left=n,last;
    while(left!=1)
    {
        for(int i=1;i<=n;i++)if(!f[i])
        {
            if(count!=m)    f[i]=1,left--;
            else count=0,last=i;
            count++;
        }
    }
    printf("%d",last);
}

void solve70()
{
    f[1]=1;
    for(int i=m;i<=n;i++)
        f[i]=f[i-m+1]%(i-m+1)+m;
    cout<<f[n];
}

long long solve100(long long x)
{
    if(x==1)    return 1;//递归出口
    return m*(solve100(x/m+x%m)-x%m);//x/m+x%m为剩下的人
}

int main()
{
//#define cena
#ifdef cena
    freopen("joseph.in","r",stdin);
    freopen("joseph.out","w",stdout);
#endif
    cin>>n>>m;
    if(n<=1000) solve30();
    else if(n<=100000)  solve70();
    else    cout<<solve100(n);

#ifdef cena
    fclose(stdin);
    fclose(stdout);
#endif
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值