N 个数围城一个圈,编号从 1 - N; 给定步长m, 第一个数开始报1,数到 m的数被删除,下个数又从1开始报数,如此循环,直到剩下最后一个数。问最后一个数的编号是?
方法1: 当 m = 2的时候有通项,参见具体数学第一章,J(2^m+p) = 2p + 1 ,其中 0<=p<2^m, m 为N的二进制形式的最高次幂。用数学归纳法可以证明。
提示: J(2n) = 2J(n)-1; J(2n+1) = 2J(n) + 1; ,对m 归纳。
当 m > 2的时候不存在通项,只有个递推式。设,J(n)为最后一个数的编号。可知,n个数,第一个删除的数一定是 k = (m-1) % n + 1; 剩下n-1个数:
k+1, k+2, ...... n, 1, 2, ..... k-1 ;下一轮从 k+1 开始报数, 也就是说对应着 1,2,.... , n-1;这样就把原来的 求J(n) 转化为求 J(n-1);假设 J(n-1)= x ,则把 x 对应回J(n)中的编号y即可, 其中, y = (x -1 + k) % n + 1; 把各个变量代入得到: J(n) = [J(n-1) - 1 + (m-1)%n+1 ] % n + 1 = [J(n-1) + (m-1)%n ] % n + 1 = ( J(n-1) + m -1 ) % n + 1; 其中 J(1) = 1.于是递归算法可得:
#include <cstdlib>
#include <iostream>
using namespace std;
int josephus(int n,int m)
{
if(n==1)
return 1;
return (josephus(n-1,m)+(m-1))%n+1;
}
int main(int argc, char *argv[])
{
int n,m;
while(n)
{
cin>>n>>m;
cout<<josephus(n,m)<<endl;
}
system("PAUSE");
return EXIT_SUCCESS;
}
方法二: 模拟
/**
* author:小旦 2012-09-16
* 暴力法
*/
#include <iostream>
using namespace std;
int K;
const int MAX = 16;
int arr[2 * MAX];
int GoodPNum,BadPNum,res;
void init()
{
for(int i=1; i<=2*K; i++) //下标为 1~2K标记为1,代表2K个人
arr[i] = 1;
GoodPNum = K; //被杀之前,好人与坏人都是K个
BadPNum = K;
}
void solve()
{
int ModNum = 2 * K + 1;
res = 2; //返回结果从2开始暴力
int count,index; //当count == res的时候,当前下标index的人被杀
while(true)
{
count = 1;
index = 1; //遍历数组的下标
while(true)
{
//下标溢出数组上界,则回到初始位置
if( index == ModNum)
index = 1;
//当前所加次数需要杀一个人
if(count == res)
{
count = 0; //置零,重新开始加
//开始杀人操作
arr[index] = -1;
//如果杀的是坏人
if(index > K)
BadPNum--; //尚未被杀的坏人数减一
else
GoodPNum--; //尚未被杀的好人数减一
//判断是否找到解
//坏人杀光了,好人还有剩
if(BadPNum == 0)
{
cout<<res<<endl;
return;
}
//好人杀光了,坏人还有剩
else if(GoodPNum == 0)
break; //当前解不是最优解,跳出最里层while
}
else {
//将浮标指到下一个可以杀的人
while(++index) {
//下标溢出数组上界,则回到初始位置
if( index == ModNum)
index = index % ModNum + 1;
//找到一个可以杀的人
if(arr[index] == 1)
break;
}
count++;
}
}
res++; //上一个res值不符合答案,加一继续暴力
}
}
int main()
{
while(cin>>K) {
init();
solve();
}
return 0;
}