猴子选大王(约瑟夫问题)

例题描述
由M只猴子围成一圈,从1到M进行编号,打算从中选出一个大王,经过协商,决定选出大王的规则:从第一个开始循环报数,
数到N的猴子出圈,下一个猴子从1开始报数。
输入样例
3 2
输出样例
3
方法一:
//模拟法

#include<iostream>
using namespace std;
#define MAX 100
long a[MAX+1]long m,n,i,Count,k;
int main()
{
	cin>>m>>n;
	for(i=1;i<=m;i++)
		a[i]=1;
	k=m;			//报数前,指向该数的前一只猴子
	for(i=1;i<=m;i++)//出圈M只猴子
	{
		Count=0;		//报数前,个数为0
		while(Count<n)//当前报数<n,继续报数
		{
			if(k==m)//从最后一个位置移动到第一个位置
				k==1;
			else
				k++;
			if(a[k]==1)//当前猴子在圈内,报数
				Count++;
		}
		a[k]=0;//猴子K出圈
	}
	cout<<k<<endl;
	return 0;
}

分析:上程序发现:当猴子出圈后,程序也会一一检查是否出圈,在这里花了大量的时间。
方法二:利用数组存放下一个该报数的编号,以8只猴子为例,a[1]的值为2,意思是指2号猴子,
如下所示

a[1]a[2]a[3]a[4]a[5]a[6]a[7]a[8]
23456781

这样构成了一个环,如果5号猴子出圈就用语句a[4]=a[a[4]],以后报到4号猴子时,就直接指向6号猴子。

#include<iostream>
using namespace std;
#define N 100000
long a[MAX+1]
long m,n,i,Count,k;
int main()
{
	cin>>m>>n;
	for(i=1;i<=m-1;i++)
		a[i]=i+1;
	a[m]=1;//最后一只猴子指向第一只
	k=m;//报数前,指向该报数的前一只猴子
	for(i=1;i<=m;i++)//出圈M只猴子
	{
		for(Count=1;Count<=n-1;Count++)//只需报数n-1次,a[k]指向该出圈瘊子
			k=a[k];
		a[k]=a[a[k]];
	}
	cout<<k<<endl;//最后一只出圈的是大王
}

为了更好的理解我列出来第一次小循环后的数组值:

a[1]a[2]a[3]a[4]a[5]a[6]a[7]a[8]
33456781

所以每次点名到1的时候回跳过二号值,外循环进行到M次时要出去的k值即为大王的位置。

方法三:
倒退算法:如果我们知道这个子问题的答案:设X是猴王,那么根据对应关系就有以下的对应关系,F[i]=(F[i-1]+k)%i;(k为每次需要喊的次数)

//倒退算法
#include<iostream>
using namespacestd;
int main()
{
	int i,m,k,ans;
	cin>>m>>k;
	ans=0;
	for(i=2;i<m=;i++)
		ans=(ans+k)%i;
	cout<<ans+1<<endl;//因为这里的位次是从0开始的k%k=0.
}

例题描述】变形约瑟夫问题
给出K与N,求K%i(1<=i<=N),即求出K%1+K%2+K%3+K+…+K%N的值。
输入格式
两个整数K与N(k>=1,N<109)。
输出格式
一个正整数ans=K%i+
样例输入
10 10
样例输出
13
方法一:

#include<iostream>//变形约瑟夫问题解法一
using namespace std;
int main()
{
	int n,k,s=0;
	cin>>n>>k;
	for(int i=1;i<=n;i++)
		s=s+k%i;
	cout<<s<<endl;
}

但由于K和N数据规模过大(K>=1,N<109),所以我们要进行优化。由于题目中没有指明K与N的大小比较,所以可能的情况有以下三种:
(1) 1<K<N
(2) 1<N=K
(3) 1<N<K
当为情况(1)时我们可以把方程划分为前后两部分

i12345678910111213141516
K%i0010142076543210

可以发现当i>K/2时,K%i的值为等差数列。
因此,我们可以将前半部分划分为前后两部分。
后面部分是一个公差为1的等差数列,即(K/2)
-1,(K/2)-2,…4,3,2,1,0,直接使用等差数列求和公式算出即[K%2-1]+[K%2-2]+[K%2-3]+…+[1]+[0].
继续讲前半部分划分当i>K/3时,是一个公差为2的等差数列,但需要注意的是,由于K/2
取整,所以其首项K%(K/2)可能为0也可能为2得等差数列,但需要注意的是,由于取整K/2取整,所以其首项K/(K%2)可能为0也可能为1。
需要注意的是,当拆分到1=<i<=sqrt(K)时,该段数据已无规律可循,所以该段直接使用朴素算法即可。
这样即使数据量再大,通过这种数学方法,它的时间复杂度仅为sqrt(K);

#include<iostream>
#include<cmath>
using namespace std;
int main()
{
	long long N,K;
	while(cin>>N>>K)
	{
		long long S,T,i,s,e,ans=0;
		S=sqrt((double)K);
		T=K/S;
		//该段为朴素算法
		for(i=1,ans=0;i<=N&&i<=T;i++)
			ans+=K%i;
		if(N>K)
			ans+=(N-K)*K;
		//该段为优化算法
		for(i=S;i>1;i--)
		{
			s=K/i;
			e=K/(i-1);
			if(N<s)
				break;
			if(N<e)
				e=N;
				ans=ans+(e-s)*(K%e+K%(s+1))/2;
		}
		cout<<ans<<endl;
	}
}

//递归算法

#include<iostream>
using namespace std;
int m,k;
int Josephus(int m)
{
	int ans;
	if(m==1)
		return 1;
	else
		ans=(Josephus(m-1)+k-1)%m+1;
	return ans;
}
int main()
{	
	cin>>m>>k;
	cout<<Josephus(m)<<endl;
	return 0;
}

这种算法效率是极高的但还可以对它进行优化,使他与侯子数无关。
将上面的递归程序中的第12行进行注释去除,再次编译运行观察输出的ans值,例如当m=30,k=3时如表6.2所示

ans1234567891011
221414714710
ans21222324252627282930
25811141720232629

可以发现从ans21到30之间的值处于一种等差递增的状态。通过分析ans=(ans+k-1)%i+1,可以看出,当i比较大而ans+k-1比较小的时候,ans就处于一种公差为K的等差递增状态,而这一部分我们可以跳过。
设中间变量x列出等式:ans+k×x-1=i+x。
解出x,令ans=ans+k*x,将i+x直接赋值给i,这样就跳过了中间共x重的循环,从而节省了等差递增得时间开销。
当然,我们还需要注意几种特殊情况:
1 当k=1时,最终结果就是(k+n-1)%M。
2 当k!=1时,最终结果就是(k+n-1)%M;
参考程序如下所示:

#include<iostream>
using namespace std;
int main()
{
	int x,M,ans=0,K;
	cin>>M>>K;
	for(int i=1;i<=M;i++)
	{
		if(ans+K<i)
		{
			x=(i+1-ans)/(K-1)-1;//该处减一是因为,循环的末尾有一个进位需要执行所以这里需要减一
			if(i+x<M)
			{
				ans=ans+k*x;//跳过一些不必要的轮次
				i=i+x;
			}
			else//该次跳跃可以执行到末尾
			{
				ans=ans+K*(M-i);
				i=M;//循环结束
			}
		}
		ans=(ans+K-1)%i+1;
	}
	cout<<ans<<endl;//输出猴王的位置
	return 0;
}

算法分析:算法优化之处就是通过观察规律而得到的,其实就通过计算x的值,减少了循环当中一些不必要的轮次,从而大大的节省了时间,以至于算法本身与M是没有关系的。

  • 18
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值