约瑟夫游戏

【问题描述】

YJC 很喜欢玩游戏,今天他决定和朋友们玩约瑟夫游戏。
约瑟夫游戏的规则是这样的:n 个人围成一圈,从 1 号开始依次报数,当报到 m 时,
报 1、2、…、m-1 的人出局,下一个人接着从 1 开始报,保证(n-1)是(m-1)的倍数。最后剩
的一个人获胜。
YJC 很想赢得游戏,但他太笨了,他想让你帮他算出自己应该站在哪个位置上。

【输入格式】

第一行包含两个整数 n 和 m,表示人数与数出的人数。

【输出格式】

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

【输入输出样例】

joseph.in joseph.out
10 10 10

【数据说明】

对于 30%的数据,满足 2≤n≤1000;
对于 50%的数据,满足 2≤n≤1000000;
对于 100%的数据,满足 2≤m≤n<2^63 -1 且(n-1)是(m-1)的倍数。

【题解】

30%:直接模拟,每次暴力删除即可。时间复杂度 O(n 2 )。

50%:递推计算。设 F[n]表示 n 个人时最后剩下的人的编号。每
增 加 m-1 个 人 , 答 案 向 后 移 动 m 位 。 于 是 递 推 式 为
F[n]=F[n-m+1]%(n-m+1)+m,初始 F[1]=1。时间复杂度 O(n/m)。

100%:容(da)易(biao)看出,只有 F[m^a +m-1]=m,其余的 F[n]
都满足 F[n]=F[n-m+1]+m。于是设 n=m^a +(m-1) k (m^a < n ≤ m^a+1 ),那
么 F[n]=km。时间复杂度 O(log m n)。

这个规律很难找,不过我输n/m*m有50分是什么鬼(这说明这个规律适合大部分情况)。。。

试着弄出规律:(据说这规律只能手算,不能证)
这个代码看得懂一些

tr=(n-1)/(m-1)-1;
    long long a=m;
    while(a*m<=n)
    {
        tr-=a;
        a*=m;
    }
    tr*=m;
    if(tr==0)tr=n;
    cout<<tr;

这个程序实际上是倒着推,a代表当前人数,每乘m就是往前倒推一轮。而tr相当于要删去的(m-1)的个数,倒数第一轮删m^0个,第二轮m^1个,第三轮m^2个…到不够减的时候,幸存者即为那个余数。所以最后能求出当只有一个人时的当前轮数tr,而当前人的编号就是tr×m。
或者换种理解:
以n=13,m=3为例:
第一次筛后:3,6,9,12
同时除m:1,2,3,4
第二轮筛后:2
ans=2×m=6得解
a*m<=n用来判断是否前面还有一轮。
那个if用来特判m^2=n;
贴上递归变递推的程序。

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#define ll unsigned long long
#define re register
#define il inline
#define fp(i,a,b) for(ll i=a;i<=b;i++)
#define fq(i,a,b) for(ll i=a;i>=b;i--)
using namespace std;
ll i;
il ll gi()
{  
  re ll x=0;
  re ll t=1;
  re char ch=getchar();
  while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
  if(ch=='-') t=-1,ch=getchar();
  while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
  return x*t;
}
int main()
{
    freopen("joseph.in","r",stdin);
    freopen("joseph.out","w",stdout);
    ll n,k;
    cin>>n>>k;
    for(i=1;i<n/k;i*=k);
    cout<<(n-i)/(k-1)*k<<endl;
    fclose(stdin);
    fclose(stdout);
    return 0;
}
posted @ 2017-08-11 22:36  小蒟蒻ysn  阅读(319)  评论(0编辑  收藏  举报