今天看到一题和约瑟夫环相关的问题,就把约瑟夫环的板子整了一下。
问题描述:n个人围成圈做,编号从0开始到n-1,从第0个人开始报数(从1开始),每次报到m的人出局,然后从后一个人开始继续往后报数。问最后获胜的人是谁。
思路:如果下标从0开始,则第一轮m-1被删除。剩下的数就又生成了一个大小为n-1的约瑟夫环。其中对应关系(旧–新)
k—-0
k+1—-1
k+2—-2
……
n-1—-n-k-1
0—-n-k
1—-n-k+1
……
k-3—-n-3
k-2—-n-2
如果说 f(n) 是n个数的约瑟夫问题的答案
那么可以得到递推式子
f(n)=(f(n−1)+m)modn
到此,我们已经有了一个 O(n) 的求法了。一下是板子
long long Josephus(long long n, long long m, long long start = 0) {
long long ans = 0;
for(long long i = 2; i <= n; i++) {
ans = (ans + m) % i;
}
return (ans + start) % n;
}
但是问题来了,如果n很大呢,如hdu3089,其中n为10的12次,但是m很小只有1000。
分析:当每次加m的时候其实很多时候ans+m并没有比i大,所以这里可以一次性加好多次,但是每次的i不一样,但是满足
ans+k∗m<i+k−1=>k∗(m−1)<i−ans−1
然后判断一下就好了,上代码,要注意题目中的下标
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<time.h>
#include<set>
#include<stack>
#include<vector>
#include<map>
#define pi acos(-1)
#define maxn 111111
#define maxm 11111
#define INF 0x3F3F3F3F
#define eps 1e-8
#define pb push_back
#define mem(a) memset(a,0,sizeof a)
using namespace std;
const long long mod = 1000000007;
/**lyc**/
void init(void) {
}
/**0到n-1下标**/
long long Josephus(long long n, long long m, long long start = 0) {
if(m == 1) {
return (start + n - 1) % n;
}
long long ans = 0;
for(long long i = 2; i <= n;) {
if(ans + m < i) {
long long step; ///判断步数
if((i - ans - 1) % (m - 1) == 0) step = (i - ans - 1) / (m - 1) - 1; ///如果能整除
else step = (i - ans - 1) / (m - 1); ///如果不能整除
if(i + step > n) return ((ans + (n - i + 1) * m) + start) % n; ///若果迭代次数够了
i += step;
ans += step * m;
}
else {
ans = (ans + m) % i; ///正常的迭代
i++;
}
}
return (ans + start) % n;
}
int main() {
init();
long long n, k;
while(scanf("%lld%lld", &n,&k) != EOF) {
long long ans = Josephus(n, k);
printf("%lld\n", ans + 1);
}
return 0;
}