约瑟夫环: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;
}