约瑟夫游戏
【问题描述】
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;
}