[线性表] 约瑟夫环

版权声明:本文为博主原创文章,若有错误之处望大家批评指正!转载需附上原文链接,谢谢! https://blog.csdn.net/summer_dew/article/details/83922097

约瑟夫问题 约瑟斯置换 丢手绢问题 约瑟夫环

问题来历

据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。

https://www.bilibili.com/video/av7885066

原始问题

N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉
例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3,1

模拟游戏过程

模拟游戏过程,循环数组报数:O(nm)

#include<iostream>
using namespace std;
main()
{
    bool a[101]={0};
    int n,m,i,f=0,t=0,s=0;
    cin>>n>>m;
    do
    {
        ++t;//逐个枚举圈中的所有位置
        if(t>n)
            t=1;//数组模拟环状,最后一个与第一个相连
        if(!a[t])
            s++;//第t个位置上有人则报数
        if(s==m)//当前报的数是m
        {
            s=0;//计数器清零
            cout<<t<<' ';//输出被杀人编号
            a[t]=1;//此处人已死,设置为空
            f++;//死亡人数+1
        }
    }while(f!=n);//直到所有人都被杀死为止
}

递推公式

参考:https://blog.csdn.net/u011500062/article/details/72855826

递归:O(n)

  1. 第一个被删除的数为 (m-1)%n。

  2. 假设第二轮的开始数字为k,那么这n - 1个数构成的约瑟夫环为k, k + 1, k + 2, k +3, …,k - 3, k - 2。做一个简单的映射。

      k      ----->  0 
      k+1    ------> 1 
      k+2    ------> 2 
      ... 
      ... 
      k-2    ------>  n-2 
    

这是一个n-1个人的问题,如果能从n-1个人问题的解推出 n 个人问题的解,从而得到一个递推公式,那么问题就解决了。假如我们已经知道了n-1个人时,最后胜利者的编号为x,利用映射关系逆推,就可以得出n个人时,胜利者的编号为 (x + k) % n。其中k等于m % n。代入(x + k) % n <=> (x + (m % n))%n <=> (x%n + (m%n)%n)%n <=> (x%n+m%n)%n <=> (x+m)%n

  1. 第二个被删除的数为(m - 1) % (n - 1)。

  2. 假设第三轮的开始数字为o,那么这n - 2个数构成的约瑟夫环为o, o + 1, o + 2,…o - 3, o - 2.。继续做映射。

      o      ----->  0 
      o+1    ------> 1 
      o+2    ------> 2 
        ... 
        ... 
      o-2     ------>  n-3 
    

这是一个n - 2个人的问题。假设最后的胜利者为y,那么n -1个人时,胜利者为 (y + o) % (n -1 ),其中o等于m % (n -1 )。代入可得 (y+m) % (n-1)
要得到n - 1个人问题的解,只需得到n - 2个人问题的解,倒推下去。只有一个人时,胜利者就是编号0。下面给出递推式:

f [1] = 0;
f [ i ] = ( f [i -1] + m) % i; (i>1)

#include <iostream>
using namespace std;
const int m = 3;
int main()
{
    int n, f = 0;
    cin >> n;
    for (int i = 1; i <= n; i++) f = (f + m) % i;
    cout << f + 1 << endl;
}

约瑟夫问题10e100版

  1. 描述
    n个人排成一圈。从某个人开始,按顺时针方向依次编号。从编号为1的人开始顺时针“一二一”报数,报到2的人退出圈子。这样不断循环下去,圈子里的人将不断减少。由于人的个数是有限的,因此最终会剩下一个人。试问最后剩下的人最开始的编号。

  2. 输入格式
    一个正整数n,表示人的个数。输入数据保证数字n不超过100位。

  3. 输出格式
    一个正整数。它表示经过“一二一”报数后最后剩下的人的编号。

  4. 样例
    样例输入1
    9
    样例输出1
    3

  5. 限制
    各个测试点1s

  6. 提示
    样例说明
    当n=9时,退出圈子的人的编号依次为:
    2 4 6 8 1 5 9 7
    最后剩下的人编号为3

先暴力打表,找到规律:

#include<cstdio>
#include<ctime>
#include<cstring>
int vis[10001]= {0};
int main() {
	freopen("output.txt","w",stdout);
	for(int z=1; z<=10000; z++) {
		memset(vis,0,sizeof(vis));
		int n,count=0;
		n=z;
		int now=1,next=1,kill=0;
		while(count<n-1) {
			do
				next=(next-1+1)%n+1;
			while(vis[next]);
			if(kill) {
				count++;
				vis[now]=1;
				kill=0;
			} else
				kill=1;
			now=next;
		}
		for(int i=1; i<=n; i++)
			if(!vis[i])
				printf("%d,",i);
	}
	return 0;
}

找到规律后,再用高精度

猜你喜欢

转载自blog.csdn.net/summer_dew/article/details/83922097