约瑟夫-从模拟到毒瘤

约瑟夫真是个好题。

约瑟夫的题有模拟,递推的标签。于是有两大类算法,三种题目。

入门练习类

复杂度$\Theta(NM)$

一般作者为了显示是个入门题会出$10^3 ~ 3 \times 10^4$的数据范围。(额,出到$10^5$仿佛还没见过)

而且一般会问整个序列。

模拟这个过程。

于是,有例子:

$5$个人报数,每报到$3$这个人就出列,下一个人从1开始报。

使用$LaTeX$。分$4$步

$$
\begin{array}{ccccc}
1&2&3&4&5\\
1&2&\not3&4&5\\
\not1&2&\not3&4&5\\
\not1&2&\not3&4&\not5\\
\not1&\not2&\not3&4&\not5
\end{array}
$$

于是最后只有$4$号留下了。

上个代码(模拟):

#include<cstdio>
int all[10000000];
int main(){
	int m,n;
	int bs=0,out=0,num=0;
//	out计数出去的人数	num记录当前的,	bs是报数计数器
    scanf("%d%d",&m,&n);
	do{
		++num;
		if(num==m+1) num=1; //循环下标
		if(all[num]==0) ++bs;//如果这个人没有出去,报数
		else continue; //已经出去了,跳过
		if(bs==n){//发现报到了
		    bs=0;
			all[num]=1;//干出去
			++out;//计数
			printf("%d ",num);//输出
		}
	}while(out!=m);//如果全出去了,结束。
	return 0;
}

递推类

时间复杂度$\Theta(N)$

于是$0<n,m<10^8$

并且只问最后一个人的编号。

于是递推。

如果我们要求$1$个人的序列的答案,

然后反向报数加人

可以先求出两个人的序列,在一直推到$N$个人,

每次重新编号,并且为了方便,我们把$[1,N]$平移到$[0,N-1]$。

于是本来有一个人。

后来的一个人排在了正确的位置上,

因为是反向报数,所以其实这个人就是最后出去的人。

而且现在的两个人中的最后出去的人的编号是对的。

于是一直推。

$$f_i=(f_{i-1}+m)\mod{i}$$

为了防止编号越出$n$,还得取个模。

放个代码(好短):

#include <iostream>
#include <cstdio>
#define LL long long

using namespace std;

LL ans=0,n,m;
int main(){
	ans=0;
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)
		ans=(ans+m)%i;
	printf("%lld\n",ans+1);
}

于是还有另类的题(不过也是逆推)

第$i$次报$i$个数

不过也一样,只是把里面的改成

for(int i=n-1;i>=0;i--)
	ans=(ans+i)%(n-i+1);

一样是逆推。

毒瘤类:

当作者不再冷静,就会把数据出成这个鬼样子:

$N\leq 10^9,M \leq 10^6$

总之$\Theta(N)$过不去了。

那么我们就要想想为啥$M$这么小,

于是可以利用类似分块(?)的思想。

在$\Theta(N)$的基础上,把不需要取模的那部分直接乘法加速。

于是变成$\Theta(M \log N)$

就终于干过毒瘤出题人啦。

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

int m,n;
int main(){
	int T;
	for(cin>>T;T;T--){
		scanf("%d%d",&n,&m);
		int id=0;
		if(n>m){
			for(int i=1;i<=m;i++)
				id=(id+m)%i;
			for(int i=m+1;i<=n;i++){
				id=(id+m)%i;
				int jumpl=(i-id)/(m-1);
				if(i+jumpl<=n){
					id=(id+jumpl*m)%(i+jumpl);
					i+=jumpl;
				}
				else{
					id=id+(n-i)*m;
					break;
				}
			}
		}else{
			id=0;
			for(int i=1;i<=n;i++)
				id=(id+m)%i;
		}
		printf("%d\n",id+1);
	}
}

原文地址:https://www.cnblogs.com/kalginamiemeng/p/11625108.html

时间: 2024-10-29 08:20:52

约瑟夫-从模拟到毒瘤的相关文章

Roman Roulette(约瑟夫环模拟)

Roman Roulette Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 286    Accepted Submission(s): 105 Problem Description The historian Flavius Josephus relates how, in the Romano-Jewish conflict of

【算法学习笔记】54.约瑟夫问题 模拟、逆推动规 SJTU OJ 1038 二哥的约瑟夫

Description 话说二哥当年学习数据结构的时候遇到了那道猴子报数的题目,其实这就是经典的约瑟夫问题. 可是当年的二哥还是个毛头小子,只会用模拟的方法,而其他同学却使用了一些令二哥完全摸不到头脑的方法. ……二哥一怒之下改了题目…… 话说当年花果山的猴子要选大王,选举办法如下: 所有猴子按1-M编号围坐一圈,二哥站在圈中心,由二哥指定一个整数Kn, 之后猴子们从1号开始按顺序报数,报到Kn的猴子退出到圈外,二哥再报出一个整数Kn+1, 然后由刚刚退出的猴子的下一只猴子再开始报数,如此循环报

10行Python代码解决约瑟夫环(模拟)

http://blog.csdn.net/dengyaolongacmblog/article/details/39208675 1 #!/usr/bin/env python 2 # coding: utf-8 3 4 import os 5 import sys 6 import string 7 import operator 8 import re 9 10 def josephus(n,k): 11 link=range(1,n+1) 12 ind=0 13 for loop_i in

JS数据结构第三篇---双向链表和循环链表

一.双向链表 在上文<JS数据结构第二篇---链表>中描述的是单向链表.单向链表是指每个节点都存有指向下一个节点的地址,双向链表则是在单向链表的基础上,给每个节点增加一个指向上一个节点的地址.然后头结点的上一个节点,和尾结点的下一个节点都指向null.同时LinkedList类中再增加一个last内部属性,一直指向链表中最后一个节点.结构模拟如图: 同样对外暴露的方法和单向链表一样,只是内部实现稍有变化 双向链表完整设计代码: /** * 自定义双向链表:对外公开的方法有 * append(e

HDU-4841 圆桌问题 STL模拟约瑟夫问题

中文题,题意一看就是卧槽,这不约瑟夫么,然后脑子一抽就用了链表写,然后果然T了,最后用Vector模拟的约瑟夫问题. #include <iostream> #include <cstdio> #include <cmath> #include <cstring> #include <queue> #include <iomanip> #include <algorithm> #include <vector>

约瑟夫环(Josehpuse)的模拟

约瑟夫环问题: 0,1,...,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字,求出这个圆圈里剩下的最后一个数字. 这里给出以下几种解法, 1.用队列模拟 每次将前m-1个元素出队,出队元素放入队列的末尾,再循环即可,这种方法时间复杂度为O(mn)(每找出一个数字需要m步运算,要找出n人数字),空间复杂度为O(n),用于存放队列,运行结果如下. 2.环形链表模 时间复杂度为O(mn),空间复杂度为O(n) 代码如下(vs2015调试正常): 1 //Josephuse环

POJ 3517 And Then There Was One(约瑟夫环-递推or模拟)

POJ 3517 题目: n  k m 数字1到n成环,先叉数字m,往下数k个,直到最后只有一个数字,输出它. 链表模拟: #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<cmath> #include<algorithm> #include<cmath> #include<vector> #incl

POJ 3750,小孩报数问题,模拟约瑟夫问题

小孩报数问题 Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 9978   Accepted: 4649 Description 有N个小孩围成一圈,给他们从1开始依次编号,现指定从第W个开始报数,报到第S个时,该小孩出列,然后从下一个小孩开始报数,仍是报到S个出列,如此重复下去,直到所有的小孩都出列(总人数不足S个时将循环报数),求小孩出列的顺序. Input 第一行输入小孩的人数N(N<=64) 接下来每行输入一个小孩

约瑟夫问题--list模拟循环链表

约瑟夫问题 Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目描述 n个人想玩残酷的死亡游戏,游戏规则如下: n个人进行编号,分别从1到n,排成一个圈,顺时针从1开始数到m,数到m的人被杀,剩下的人继续游戏,活到最后的一个人是胜利者. 请输出最后一个人的编号. 输入 输入n和m值. 输出 输出胜利者的编号. 示例输入 5 3 示例输出 4 首先说一下写这个之前我是准备徒手艹链表的,可惜意志力实在不咋滴,再加上手头上没课本,之前我有看过