约瑟夫环问题总结 poj3517,

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37632935/article/details/79773753

约瑟夫环:n个人报数,报到m的人出队,然后继续报,问最后出队人的编号。

最简单的方法链表模拟一下。而n很大时链表也无法在短时间得到结果。所以要找他的数学解法。

公式为f[i]=(f[i-1]+m)%i  (i从2循环到n)。

这个公式推导过程如下。

现将n个人重新编号为0~n-1

(1)   0,1,2,3,4,5,6,7.......n-1

取出第m个,即删掉编号m-1。

(2)   0,1,2,3,4,5,7,m-2,m,m+1......n-1

我们可以吧m左边的人移到n-1右边,即

(3)   m,m+1....n-1,n,n+1,n+m-2

我们发现序列又变成连续的序列。

然后序列重新从0到n-2编号

(4)   0,1,2,3,4,.......n-2

一直这样重复下去,最后只剩下一个0.

f[1]=0;

那么如何从f[1]推f[2]呢。


观察发现(4)式每个数+m即可得到(3)式。

(3)式模上当前人数n即可得到(2),

于是就推出了上面的公式:

f[i]=(f[i-1]+m)%i。


好了,有了这个前置技能,我们可以做题了,

首先第一题poj3517。

n个人吗,每次k步,起始位置为m,且m先被杀死。

1,2,3,4,5,6,7....m....n。

那么我们从第一步先杀死m,就相当于第一步是走了m步,而不是k步,所以

可以先递推前n-1项,然后处理f[n]时让它加上m,而不是加上k。

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
using namespace std;
typedef long long ll;
ll f[101000];
int main()
{
	ll n,m,k;
	while(cin>>n>>k>>m)
	{
		if(n==0||m==0||k==0) break;
		f[1]=0;
		for(int i=2;i<n;i++)
		{
			f[i]=(f[i-1]+k)%i;
		}
		f[n]=(f[n-1]+m)%n;
		cout<<f[n]+1<<endl;
	}
	return 0;
}

poj1781,这题m固定是2,但n很大,所以要打表找规律。

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
using namespace std;
typedef long long ll;

int p[30];  
int main()  
{  
    for(int i = 0; i <= 27; i++)  
    {  
        p[i] = 1<<i;  
    } 
    char s[10];  
    int i;  
    while(~scanf("%s",s))  
    {  
        if(strcmp(s,"00e0") == 0) break;  
        int n;  
        n=(s[0]-'0')*10+s[1]-'0';
        for(i=1;i<=s[3]-'0';i++)  
        n=n*10;
        for(i=0;i<=27;i++)  
            if(p[i]>=n)  
                break;  
        if(p[i] == n)  
            printf("1\n");  
        else  
            printf("%d\n",n-(p[i]-n-1));  
    }  
    return 0;  
}


阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页