NOIP模擬賽20191024 T1 嘟嘟嚕【約瑟夫問題的mlogn解法】

題目描述:

在這裏插入圖片描述
在這裏插入圖片描述

題目分析:

(Steins Gate,給出題人點贊!好評!)這麼多次模擬賽第一次AK

我們把0~n-1這n個人排成一排:
0 1 2 3 … n-1
然後編號爲m%n-1的人會被機關處決,從編號爲m%n的人開始重新計數,假設m%n=4:
0          1          2        3   4   5   6   n1n4   n3   n2         0   1   2   n5\begin{aligned} 0~~~~~~~~~~1~~~~~~~~~~2~~~~~~~~3~~~4~~~5~~~6\dots~~~n-1\\ n-4~~~n-3~~~n-2~~~~~~~~~0~~~1~~~2\dots~~~n-5 \end{aligned}

可以看出這一輪相對於下一輪編號增加了4(即m%n),所以下一輪編號爲k的人在這一輪編號爲(k+m%n)%n

f[n]f[n]表示n個人中倖存者的編號,那麼有f[n]=(f[n1]+m%n)%nf[n]=(f[n-1]+m\%n)\%n

當n<=106時可以O(n)遞推,但是n<=109就行不通了。

注意到題目中m<<n,而當m<n時m%n=m,所以可以嘗試先把前m項遞推求出來,然後式子就變爲了:f[n]=(f[n1]+m)%n   (mn)f[n]=(f[n-1]+m)\%n~~~(m\le n)

而m<<n,可以看出我們可以嘗試一次遞推多輪而不必管模。
假設我們當前推到了f[j]f[j],需要找到使得f[j]+(ij)m>if[j]+(i-j)m>i成立的最小的ii,簡單化簡可以得到i=jmf[j]m1+1i=\lfloor\frac {jm-f[j]}{m-1}\rfloor+1,於是可以直接令f[i]=(f[j]+(ij)m)%if[i]=(f[j]+(i-j)m)\%i

經過測試發現這樣的遞推方式跑的很快(於是我就不管複雜度什麼的了,和O(n)遞推拍一拍之後直接下一題

考完題解是這樣的:
在這裏插入圖片描述
真是有yali的風範(我TM上不去Wikipedia啊啊啊啊啊啊

然後我找到一篇文章:約瑟夫問題(Josephus problem)的klog(n)解法
文章的末尾有複雜度分析(然而我並沒有看懂第一步是怎麼來的,於是就咕咕咕了

Code(代碼是按照從1開始編號的方式寫的,不如從0開始的簡潔,讀者可以自行嘗試):

#include<bits/stdc++.h>
using namespace std;
int T,n,m;
int main()
{
	freopen("mayuri.in","r",stdin);
	freopen("mayuri.out","w",stdout);
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		if(m==1) {printf("%d\n",n);continue;}
		int f=1;
		for(int i=2;i<=min(n,m);i++) f=(f+(m-1)%i)%i+1;
		for(int j=m,i;j<n;j=i){
			i=min(1ll*n,(1ll*j*m-f)/(m-1)+1);
			f=(f+1ll*(i-j)*m-1)%i+1;
		}
		printf("%d\n",f);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章