约瑟夫问题
推荐一个博客:约瑟夫环问题详解
约瑟夫问题
约瑟夫问题(有时也称为约瑟夫斯置换),是一个出现在计算机科学和数学中的问题。在计算机编程)的算法中,类似问题又称为约瑟夫环。
人们站在一个等待被处决的圈子里。 计数从圆圈中的指定点开始,并沿指定方向围绕圆圈进行。 在跳过指定数量的人之后,执行下一个人。 对剩下的人重复该过程,从下一个人开始,朝同一方向跳过相同数量的人,直到只剩下一个人,并被释放。
问题即,给定人数、起点、方向和要跳过的数字,选择初始圆圈中的位置以避免被处决。
简单约瑟夫环问题
给出n个人,间隔k个人,请这个人出列,一直这样下去,最后出列的人是谁?
递归解法
int fun(int n,int k){
if(n==1)return 0;
else return (fun(n-1,k)+k)%n;
} 下面来解释一下,假设
| 在环n的情况下 | 在环n-1的情况下 |
|---|---|
| 0 | n-3 |
| 1 | n-2 |
| 2 | 被请出列了 |
| 3 | 0 |
| 4 | 1 |
| ... | 2 |
| n-3 | 3 |
| n-2 | 4 |
| n-1 | 5 |
这里的fun都是对一个子问题的求解,强行解释为.可以去上面推荐的博客看看,有图会看得更清楚.
进阶约瑟夫环问题
题目传送门:Gym - 101955K
找出第M个出列的人
所以这是题解?(雾
第个出列的人最后所在的环是
环,想知道算怎么来的?把
代进去验证一下就好了啊(逃.其实是因为把第
个人请出列之后就是一个
的环了.所以前一个就是第
个出列的人最后所在的环.
知道n-m+1的状态了,第个出列的人在
环上是在(k-1)的位置上(像上面一样用子问题,倒推出主问题).所以用上面的直接跑?
你想用O(n)跑?做你的大头梦去.
如果要跨环的时候还是要用上面的跑的,其他的可以来神仙优化一下,在一个环下,一次跑完它,而不要一步一步跑.这个是你在当前环最多能跑的长度.怎么算的?(留坑).然后把这个数除以
就是你在这个环上一次能跑多少次.
为什么除以?因为你往回推的时候,别人已经把位置为
的人给请出列了,所以你只能剩下
的人在这个环里面,所以你除以这个才能知道你在这个环上能跑多少次.倒推到n环就要退出了,所以你在跑一整个环的时候,要取个min值,当前还能跑得环数和当前环能跑的次数.
用上面的算法的时候,要特判一下,
的时候直接输出就好了,不然会RE.
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <string>
#include <vector>
using namespace std;
int main() {
int T;
scanf ( "%d", &T );
for ( int kase = 1; kase <= T; kase++ ) {
long long n, m, k;
scanf ( "%lld %lld %lld", &n, &m, &k );
long long ans = 0;
if ( k == 1 ) {
printf ( "Case #%d: %lld\n", kase, m );
continue;
} else {
ans = ( k - 1 ) % ( n - m + 1 );
for ( long long i = n - m + 2; i <= n; ) {
if ( ans + k >= i ) {
ans = ( ans + k ) % i;
i++;
} else {
long long t = min ( n - i + 1, ( i - ans -2 ) / (k-1) );
ans += t * k;
i += t;
}
}
}
printf ( "Case #%d: %lld\n", kase, ans + 1 );
}
return 0;
}