今天看到一題和約瑟夫環相關的問題,就把約瑟夫環的板子整了一下。
問題描述: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;
}