OI养老专题02:约瑟夫问题求幸存者

  如题。人数为n(1<=n<=30000),共k(1<=k<=30000)组数据,所报的数m恒为2,只要求输出幸存者。


  如果你还不知道什么是约瑟夫问题...——https://www.cnblogs.com/akura/p/10758080.html

  如果直接暴力枚举,那么时间复杂度就为O(NM)=O(N),所有数据一共O(KNM)=O(KN)。遇上这道题就爆掉了。

  那么怎么解决这种大数据的题呢?我们先手玩一把n个人的约瑟夫问题。由于每次对于n取模后的值在[0,n-1]之间,所以我们干脆让所有人的编号减1,也就是:0,1,2,......n-1。并且假设他们也从0~n-1报数。那么第一轮报数之后,出局的人就是:m%n-1。设tn=m%n,那么去掉了编号为tn-1的人之后,得到的序列就为:tn,tn+1......n-2,n-1,0,1,2......tn-2(tn-1出局了,所以没有),而他们报的数依次为0,1,2......n-1。不难发现,两个序列每个对应的元素相差为tn

  现在我们再手玩一把人数为n-1的约瑟夫问题。第一轮报数后,出局的人就是m%(n-1)-1。类似地,设tn-1=m%(n-1),剩下的序列就为:tn-1,tn-1+1......n-3,n-2,0,1,2......tn-1-2,他们报的数依次为0,1,2......n-2。两个序列每个对应的元素相差为tn-1。那么我们脑补一下递归的过程,最后我们会递归到边界:人数为1的情况。此时的幸存者就是0(编号减了1)。所以我们可以考虑通过人数少的来推出人数多的。设人数为n-1时的幸存者为ans,通过刚才的推导,不难发现当人数为n时幸存者就是(ans+m%n)%n。所以我们可以根据人数为1时幸存者为0递推上去,时间复杂度为O(N)。

  由于本题有很多组数据,而我们又得出了递推公式,我们可以离线处理,用f(i)表示当人数为i时幸存者的编号,则f(i)=(f(i-1)+m)%n,每次询问时回答即可,时间复杂度为O(N+K)。或者......在线处理也是没问题的,只要加上神奇的倍增。下面再深入介绍一下倍增的做法。

  不难发现,当n非常大,而m又小得可怜(比如本题)时,ans每次增加m之后对n取模还是等于ans+m,也就是说ans只增加了m。如果还是一次一次地加m的话,后面对n取模的语句始终是用不到的。所以不妨加快这个过程?设ans+m*x<n+x并且ans+m*(x+1)>=n+x+1,也就是说ans最多增加x次之后仍然小于n,即对n取模的语句无用。至于为什么n要加x,是因为往上递推了x次之后,人数也会增加x(注意我们是从人少的情况往上递推!)。分离参数之后得x<(n-ans)/(m-1),x>=(n-ans)/(m-1)-1,可得x=floor((n-ans)/(m-1))。所以我们每次累加上x*m即可,并把循环的参数i调高x。不过要注意一些细节:

  1.当ans+m>n时直接上递推式子即可。

  2.当i+x>=n时就累加不到x次了,只能加n-i+1次。

  在线倍增的时间复杂度为O( ∑(1,n) ceil( i/ floor( i/m ) ) )≈O( ceil( n2/ floor( n2/m ) ) ),所以,复杂度接近O(M)?那么所有数据一共O(KM)。在线算法的尊严!可惜只在本题这种m=2的情况下可能跑得过离线。

上一篇:python第十八天


下一篇:现场打印智能无线PDA安卓POS 条码识别、打印、数据采集销售开单收银管理软件