有关经典约瑟夫问题的几种解法

  

  约瑟夫问题是信息学奥赛中的一类经典且重要的题型,在平常的测试中屡屡出现。

 

  通常题设可抽象为:一开始有 $n $个人围成一个圈, 从 $1 $开始顺时针报数, 报出 $m $的人被踢出游戏.。然后下一个人再从$ 1 $开始报数,直到只剩下一个人。

     或者:曾经有个人在他身边,然而现在只剩他一个人。$Who$  $are$  $you$$?$  $Who$ $am$ $I$$?$  $Why$ $am$ $I$ $here$$? $走的越来越慢,人越来越少,可终于还是只剩一个了呢。他们围成一圈,随机了一个人作为$1$号,然后逆时针依次编号。$1$号开始报数,报到 $1$,他走了;然后$2$号开始报数,$2$号报了$1$,$3$ 号报了$2$ ,于是$3$ 号也走了……每一轮都从上一次出局的下一个人开始报数,第 $i$轮从$1$ 报到$i$ ,报 $i$的人出局。直到只剩他一个人。却早已不记得他自己是谁。

  针对不同的数据范围,可以存在如下几种做法:

1. $O(nm)$

  $O(nm)$的复杂度适用于$n,m$都在$30000$以内的情况,此类题型较少,例如“约瑟夫游戏”一题,$n,m<=30000$,由于随着游戏的不断进行,需要枚举的人数越少,所以复杂度实际低于$O(nm)$。算法思路:暴力模拟即可。

  

#include<bits/stdc++.h>
using namespace std;
int T,N,M; bool v[1000100];
void wk(){
    memset(v,0,sizeof(v));
    scanf("%d%d",&N,&M);
    int t=0,num=0,pos=1;
    while(1){
        if(v[pos]){
            ++pos;
            if(pos==N+1) pos=1;
            continue;
        }
        ++num;
        if(num==M){
            if(t==N-1){
                printf("%d\n",pos);
                return;
            }
            v[pos]=1,++t,num=0;
        }
        ++pos;
        if(pos==N+1) pos=1;
    }
}
int main(){
    scanf("%d",&T);
    while(T--) wk();
    return 0;
}

暴力模拟约瑟夫问题

2.$O(n)$

  $O(n)$算法已经适用于大多数约瑟夫问题,让$n<=1e7$的数据范围可以被轻松解决,考虑以任意一人为起点,选出第$m$个人后的编号变化,设起始$id==0$,选出第$m$个人后,$id->(id+m)$,再回归到原来的圆形,设$i$表示第$i$轮游戏,那么整体的公式即为$(id+m)$%$(n-i+1)$。倒序枚举即可。也可以用$dp$方式实现,或者正序枚举,将公式改变为$(id+m)$%$(i+1)$,最后答案即为$id+1$。

  

#include<bits/stdc++.h>
#define re register
using namespace std;
int T,n,ans,m;
inline int read(){
    re int a=0,b=1; re char ch=getchar();
    while(ch<‘0‘||ch>‘9‘)
        b=(ch==‘-‘)?-1:1,ch=getchar();
    while(ch>=‘0‘&&ch<=‘9‘)
        a=(a<<3)+(a<<1)+(ch^48),ch=getchar();
    return a*b;
}
signed main(){
    T=read();
    while(T--){
        n=read(),m=read(),ans=0;
        if(m==1){printf("%d\n",n);continue;}
        for(re int i=n;i>=1;--i)
            ans=(ans+m)%(n-i+1);
        printf("%d\n",ans+1);
    }
    return 0;
}

O(n)递推约瑟夫问题

3.$O(mlogn)$

  此类算法并不常见,但由于一些毒瘤出题人缘故,针对$n<=1e9,m<=1e5$类型的数据范围,我们不得不采用特别的递推方式,通过打表可以发现,保持$m$不变,$n$每加一,答案在模$n$意义下加$m$,注意:此时的$n$是一个变化的$n$,那么可以通过对$n$的递推处理,将$O(n)$级别的枚举,转化为在答案值域区间上的选择性跳跃,从而将以$n$为基础的算法转向以$m$为基础的算法,可以处理该类毒瘤问题。

  

#include<bits/stdc++.h>
#define int long long
#define re register
using namespace std;
int t,n,m;
inline int read(){
    re int a=0,b=1; re char ch=getchar();
    while(ch<‘0‘||ch>‘9‘)
        b=(ch==‘-‘)?-1:1,ch=getchar();
    while(ch>=‘0‘&&ch<=‘9‘)
        a=(a<<3)+(a<<1)+(ch^48),ch=getchar();
    return a*b;
}
signed main(){
    t=read();
    while(t--){
        n=read(),m=read();
        re int now=1,ans=1,nxt;
        while(now<=n){
            nxt=(now-ans)/(m-1);
            if(now+nxt>=n){
                ans=ans+(n-now)*m;
                break;
            }
            now=now+nxt+1;
            ans=(ans+(nxt+1)*m-1)%now+1;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

O(mlogn) 基于值域的约瑟夫问题

  至此,通过不同的数据范围选择不同的算法,一般的约瑟夫问题已经可以完全解决。

                            $Over$

原文地址:https://www.cnblogs.com/Hzoi-lyl/p/11624690.html

时间: 2024-11-12 23:58:31

有关经典约瑟夫问题的几种解法的相关文章

*HDU 1394 经典逆序数的四种解法

1.暴力 [代码]: 1 /*HDU1394暴力写法*/ 2 #include <iostream> 3 #include <string.h> 4 #include <stdio.h> 5 6 using namespace std; 7 8 int A[50005]; 9 int Low[50005],Up[50005]; 10 int main(){ 11 int n; 12 while(~scanf("%d",&n)){ 13 int

约瑟夫问题的一种解法

/* * 问题原型 */ 41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止.约瑟夫将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏. /* * 问题分析 */ 这个问题还有一种描述为丢手绢问题,因此代码命名以shoujuan 由于最近在学习java,因此决定用java链表来完成这个问题.设计一个循环链表,将41个人链接起来,选定一个人作为起始,逢3进行remove动作. 需要设计类: shoujuan,用以表

java并发编程--一道经典多线程题的2种解法

问题的描述 启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10, 然后是线程3打印11,12,13,14,15. 接着再由线程1打印16,17,18,19,20....以此类推, 直到打印到75. 程序的输出结果应该为: 线程1: 1 线程1: 2 线程1: 3 线程1: 4 线程1: 5 线程2: 6 线程2: 7 线程2: 8 线程2: 9 线程2: 10 ... 线程3: 71 线程3: 72 线程3: 73 线程3: 74 线程3: 75

TSP问题的n种解法-开篇

想法初衷: 好久前就有整理各种常规智能算法的想法,一直没找到合适的契机.最近写这方面系列文章的想法越来越强烈,一方面是想锻炼一下自己的表述能力,另一方面出于知识整理与分享,就动笔了,相信经典的东西一定会有它的用处的.算法会在统一的matlab框架下进行,即利用各种算法解决TSP问题,在应用中掌握算法的思想. 问题描述: 旅行推销员问题(TSP,Travelling Salesman Problem, 又称为旅行商问题.货郎担问题.TSP问题)是一个多局部最优的最优化问题:有n个城市,一个推销员要

整数拆分问题的四种解法【转载】

http://blog.csdn.net/u011889952/article/details/44813593 整数拆分问题的四种解法 原创 2015年04月01日 21:17:09 标签: 算法 / 母函数定理 / 五边形数定理 / acm / 动态规划 整数划分问题是算法中的一个经典命题之一 所谓整数划分,是指把一个正整数n写成如下形式: n=m1+m2+m3+....+mi;(其中mi为正整数,并且1<=mi<=n),则{m1,m2,m3,....,mi}为n的一个划分. 如果{m1,

【转载转载转载!】整数拆分问题的四种解法--尼奥普兰

整数拆分问题的四种解法 原创 2015年04月01日 21:17:09 标签: 算法 / 母函数定理 / 五边形数定理 / acm / 动态规划 整数划分问题是算法中的一个经典命题之一 所谓整数划分,是指把一个正整数n写成如下形式: n=m1+m2+m3+....+mi;(其中mi为正整数,并且1<=mi<=n),则{m1,m2,m3,....,mi}为n的一个划分. 如果{m1,m2,m3,....,mi}中的最大值不超过m,即max{m1,m2,m3,....,mi} <= m,则称

]Leetcode]-[Reorder List ]-三种解法

Given a singly linked list L: L0→L1→…→Ln-1→Ln,reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→… You must do this in-place without altering the nodes' values. For example,Given {1,2,3,4}, reorder it to {1,4,2,3}. 题目的意思就是,给定一个链表,从两头开始链接, 比如1-2-3-4-5-6,最开始取两头,组成1-

hdu 4521 小明系列问题——小明序列 (间隔至少为d的LIS 两种解法)

先附上资源地址:http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html 进程(process)和线程(thread)是操作系统的基本概念,但是它们比较抽象,不容易掌握. 最近,我读到一篇材料,发现有一个很好的类比,可以把它们解释地清晰易懂. 1. 计算机的核心是CPU,它承担了所有的计算任务.它就像一座工厂,时刻在运行. 2. 假定工厂的电力有限,一次只能供给一个车间使用.也就是说,一个车间开工的时候,其他车间都必须停工

UVALive 6257 Chemist&#39;s vows --一道题的三种解法(模拟,DFS,DP)

题意:给一个元素周期表的元素符号(114种),再给一个串,问这个串能否有这些元素符号组成(全为小写). 解法1:动态规划 定义:dp[i]表示到 i 这个字符为止,能否有元素周期表里的符号构成. 则有转移方程:dp[i] = (dp[i-1]&&f(i-1,1)) || (dp[i-2]&&f(i-2,2))     f(i,k):表示从i开始填入k个字符,这k个字符在不在元素周期表中.  dp[0] = 1 代码: //109ms 0KB #include <ios