约瑟夫问题
【题目描述】
有一圈N个人从0到N-1按顺时针编号,他们从0号开始顺时针从1到K报数,报到K的退出,然后从下一个人开始继续从1报数,直到只剩一人。给定N和K,求最后剩的人是几号。
【输入格式】
第一行两个数N,K。
【输出格式】
一个数,最后剩的人的编号。
【样例】
johnseph.in | johnseph.out |
5 3 | 3 |
【数据规模】
对于30%的数据,N≤1000;
对于100%的数据,N≤100000。
这是一个经典经典问题。我用的模拟,链表用dancing links删除操作比较容易。l[r[i]] = l[i]; r[l[i]] = r[i],只有40分。
如果用数学方法的话,递推。
最后一次报数,只剩下1个人,编号为0,出队的是0,。f[1] = 0
这是我们要求出,他在还剩2个人时的编号,是(f[1]+k)%2
当剩下m个人的时候,他的编号是,(f[m-1]+k)%m。
(因为当剩下m-1的时候,我们从0到m-2编号,实际上相当于往后平移了k,要复原我们就加上k再取余)
当剩下n的时候,我们就求出了最后剩下的人在此时的编号是(f[n-1]+k)%n。
输出f[n] + 1
参见百科:
我们知道第一个人(编号一定是m mod n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m mod n的人开始):
k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2
并且从k开始报0。
现在我们把他们的编号做一下转换:
k --> 0
k+1 --> 1
k+2 --> 2
...
...
k-2 --> n-2
k-1 --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x'=(x+k) mod n
如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:
令f表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]
递推公式
f[1]=0;
f=(f+m) mod i; (i>1)
有了这个公式,我们要做的就是从1-n顺序算出f的数值,最后结果是f[n]。因为实际生活中编号总是从1开始,我们输出f[n]+1
#include <cstdio>
long nxt[100010];
long pre[100010];
int main()
{
freopen("johnseph.in","r",stdin);
freopen("johnseph.out","w",stdout);
long n;
long k;
scanf("%ld%ld",&n,&k);
long f = 0;
for (long i=2;i<n+1;i++)
{
f = (f+k)%i;
}
printf("%ld",f);
return 0;
}