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 …     n − 1 n − 4     n − 3     n − 2           0     1     2 …     n − 5 \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} 0          1          2        3   4   5   6   n1n4   n3   n2         0   1   2   n5

可以看出这一轮相对于下一轮编号增加了4(即m%n),所以下一轮编号为k的人在这一轮编号为(k+m%n)%n

f [ n ] f[n] f[n]表示n个人中幸存者的编号,那么有 f [ n ] = ( f [ n − 1 ] + m % n ) % n f[n]=(f[n-1]+m\%n)\%n f[n]=(f[n1]+m%n)%n

当n<=106时可以O(n)递推,但是n<=109就行不通了。

注意到题目中m<<n,而当m<n时m%n=m,所以可以尝试先把前m项递推求出来,然后式子就变为了: f [ n ] = ( f [ n − 1 ] + m ) % n     ( m ≤ n ) f[n]=(f[n-1]+m)\%n~~~(m\le n) f[n]=(f[n1]+m)%n   (mn)

而m<<n,可以看出我们可以尝试一次递推多轮而不必管模。
假设我们当前推到了 f [ j ] f[j] f[j],需要找到使得 f [ j ] + ( i − j ) m > i f[j]+(i-j)m>i f[j]+(ij)m>i成立的最小的 i i i,简单化简可以得到 i = ⌊ j m − f [ j ] m − 1 ⌋ + 1 i=\lfloor\frac {jm-f[j]}{m-1}\rfloor+1 i=m1jmf[j]+1,于是可以直接令 f [ i ] = ( f [ j ] + ( i − j ) m ) % i f[i]=(f[j]+(i-j)m)\%i f[i]=(f[j]+(ij)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);
	}
}
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值