Cpp环境【CQYZOJ3531】【CQNOI2016模拟赛(八中出题)】约瑟夫の秘制游戏

【问题描述】  

  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

【来源】    

八中供题

【原题传送矩阵】

CQYZOJ3531 原题传送矩阵

【思路梳理】

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

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

  看数据规模,30分模拟,70分似乎应该是考虑贪心或者动态规划,而100分则达到了这里写图片描述,不可能通过存储实现,应该是使用递归调用,将结果作为参数或者返回值传递后输出。

算法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。

【Cpp代码】
#include
#include
#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<long long solve100(long long x)
{
    if(x==1)    return 1;//递归出口
    return m*(solve100(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<#ifdef cena
    fclose(stdin);
    fclose(stdout);
#endif
    return 0;
}

你可能感兴趣的:(基础算法之三,回溯算法,基础算法之八,递推算法,难度评级,Patience,Required)