约瑟夫环问题与优化

https://blog.csdn.net/a_forever_dream/article/details/100769164

取模法

n n n为问题规模(人数),每 k k k个人枪毙一个。全局次数 i \rm i i从1轮转到 n \rm n n,每次问题规模减小1。

#include <cstdio>
long long n, k, ans=0;
int main()
{
	scanf("%d %d",&n,&k);
	for(int i=1;i<=n;i++)
		ans=(ans+k)%i;
	printf("%d",ans+1);
}

追及法

由于除法和取模运算很耗费时间,所以我们可以减少取模次数。
在上一问中,如果 k \rm k k值很小,那么会有很多次枪毙轮转过程的取模都是无效的。所以我们可以将这些过程一次性完成。一次进行 p \rm p p次,全局次数 i \rm i i增加 p \rm p p a n s \rm ans ans增加 a n s × k \rm ans\times k ans×k

#include <cstdio>
#include <cstdlib>
#include <cstring>
#define ll long long

ll n,k;

int main()
{
	scanf("%lld %lld",&n,&k);
	ll i=1,ans=0,p;
	//本来i应该从0开始,但是0需要特判掉,懒得打特判,于是从1开始
	while(i<n)
	{
		p=(i-ans)/(k-1)+((i-ans)%(k-1)!=0);//距离 除以 速度的差 得到 时间
		//但是因为这里是整除,所以如果(i-ans)不是(k-1)的倍数的话,还需要多走一次,这个想想就能明白
		if(i+p>n)p=n-i;//要判断是否大于n
		ans+=p*k;//ans走
		i+=p;//i走
		ans%=i;//取模
	}
	printf("%lld",ans+1);//别忘了+1
}

由于我们的初衷是减少除法带来的开销,我们还可以将追及时间 p \rm p p用一种更好的方法计算出来:

< N / M > = [ ( N − 1 ) / M ] + 1 , ( 0 < M < = N , M , N ∈ Z ) <N/M>=[(N-1)/M]+1 ,\quad (0<M<=N,M,N∈Z) <N/M>=[(N1)/M]+1,(0<M<=N,M,NZ)

int n=(N-1)/M +1
//在本问中:
p=(i-ans-1)/(k-1)+1;

扩展题目:明哥之问

描述

唐吉诃德多弗朗明哥率领手下击败力库一族成为了德雷斯罗萨的新国王。有一天他要处决一批力库一族的余党,此时他突发奇想制定了一条有趣的规则:将所有人从1开始编号,第一个人从1开始报数,每次数到m的人被处决,后面的人再从1开始报数…依此循环往复,直到剩下最后一人。居鲁士非常幸运地成为了那最后一人,但明哥要求他回答这批待处决的力库一族的余党至少有多少人,如果答对就可以放他一条生路。居鲁士只知道自己的编号是k,你可以帮他回答明哥的“死亡之问”吗?

关于输入

两个整数m和k,用逗号分隔。m和k的含义如上所述。

关于输出

一个整数,表示这批待处决的力库一族的余党至少有多少人。

例子输入

3,1

例子输出

1

程序

#include <stdio.h>
#define ll long long
ll m, k, n;

int main()
{
    scanf("%lld,%lld", &m, &k);
    ll i = 1, ans = 0, p, n = k - 1;
    while (++n)
    {
        while (i < n)
        {
            p = (i - ans - 1) / (m - 1) + 1;
            if (i + p > n)
                p = n - i;
            ans += p * m;
            i += p;
            ans %= i;
        }
        if (ans + 1 == k)
            break;
    }
    printf("%d", n);
}

这个问题可以说明移动量较小时的取模法耗时之高。对比:

#include <stdio.h>
long long m, k, ans = 0, n;
int main()
{
    scanf("%d,%d", &m, &k);
    n = k-1;
    int i;
    while (++n)
    {
        ans = 0;
        for (i = 1; i <= n; i++)
            ans = (ans + m) % i;
        if (ans + 1 == k)
            break;
    }
    printf("%d", n);
}
/*
ProgramError: Case 6. Time out.
Error 1 in case 6.
Case 0: Time = 5ms, Memory = 264kB.
Case 1: Time = 61ms, Memory = 264kB.
Case 2: Time = 133ms, Memory = 264kB.
Case 3: Time = 3ms, Memory = 264kB.
Case 4: Time = 39ms, Memory = 264kB.
Case 5: Time = 5ms, Memory = 264kB.

Empty output file: '6.out'
*/

对比取模法:

Case 0: Time = 1ms, Memory = 264kB.
Case 1: Time = 1ms, Memory = 264kB.
Case 2: Time = 1ms, Memory = 264kB.
Case 3: Time = 1ms, Memory = 264kB.
Case 4: Time = 1ms, Memory = 264kB.
Case 5: Time = 1ms, Memory = 264kB.
Case 6: Time = 6ms, Memory = 264kB.
Case 7: Time = 6ms, Memory = 264kB.
Case 8: Time = 1ms, Memory = 264kB.
Case 9: Time = 2ms, Memory = 264kB.

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值