题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2211

杀人游戏

Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 3624    Accepted Submission(s): 1182

Problem Description
不知道你是否玩过杀人游戏,这里的杀人游戏可没有法官,警察之类的人,只有土匪,现在已知有N个土匪站在一排,每个土匪都有一个编号,从1到N,每次杀人时给定一个K值,从还活着的土匪中,编号从小到大的找到K个人,然后杀掉,继续往下,直到找遍,然后继续从剩下的土匪中,编号从小到大找到第K个活着的土匪,然后杀掉。比如,现在有10个土匪,K为3,第一次杀掉3,6,9号的土匪,第二次杀掉4,8号土匪,第三次杀掉5号土匪,第四次杀掉7号土匪,第五次杀掉10号土匪,我们看到10号土匪是最后一个被杀掉的(从1到K-1的土匪运气好,不会被杀!)。现在给定你一个N和一个K,问你最后一个被杀掉的土匪的编号是多少。
 
Input
第一行有一个T(T<=10000),接下来有T组数据,每组中包含一个N(N<2^31)和一个K(3<=K<=100&&K<N)。
 
Output
对于每组数据,输出最后被杀的土匪的编号。
 
Sample Input
1
10 3
 
Sample Output
10
 
这个题是约瑟夫问题,做法有几个,整理了以下,代码来自他人博客。
 

解法一:插数法(来源:http://www.cnblogs.com/yuyixingkong/p/3254566.html

 #include<stdio.h>
int main()
{
__int64 t,n,k,count,luck,s,p,i;
scanf("%I64d",&t);
while(t--)
{
scanf("%I64d%I64d",&n,&k);
p=k-;//插入周期eg:k=3,那么p=2;两个一插
for(s=k;s<n+1;)//s为标记,来标记luckboy的位置
{
i=(s-)/p;//计算s前面插几个
luck=s;//luck是记录上一个s的值
s=s+i;
}
printf("%I64d\n",luck);
}
return ;
}

以样例代码为例(如下图所示),即参数为10,3.S代表最后一个被杀的人,下面表示如果杀到最后一轮,3号是最后一个被杀掉的。那么倒数第二轮他应该在四号位置,并且倒数第二轮应该比倒数第一轮多一个人。依次类推,倒数第三轮他在五号位置,此时有六个人。需要注意的是S后面的人其实是可有可无的,对结果不影响,观察第三行和第二行,可以得到,输入(5,3)和(6,3)都是一样的结果,结果都是S(5)。第四行就可以得到输入(7,3,)(8,3)(9,3)都是得到S(7)。这样通过下面的流程图可以得到任意(x,3)问题的解。那么怎么得到所有的解呢。

【acm】杀人游戏(hdu2211)-LMLPHP

我们可以看到,第三行比第二行新插进来1个数,而第四行比第三行多插2个,这规律就是,对于第x个位置的数,前一轮(即上图的下一行)要比现在的位置靠后 (x-1)/(k-1)个,x-1好理解,k-1怎么理解呢,k是每k个人,杀一个,杀完之后,如果要还原回原来个数的人,那么需要每k-1个人插一个。 所以对于现在第x位置的人,x的前一轮的位置就是 x+(x-1)/(k-1)。

关于x的初值,显然x最小应该等于k,一直增加到等于n,结束。以上图为例,n=10,k=3,x才开始为3,一直增长到10结束,结果为10。

n =10,k=3,a=10。其实当k=3时,n=10,是a=10的下限,我们在图中可以看出来当k=3时,n属于10~13,a=10。

同理,随便输一个数 n=10096,k=28,得到 a =9776。

【acm】杀人游戏(hdu2211)-LMLPHP

符合k=28,a =9776,这样的n的范围是[9776,10137].

方法二:递归法

int f(int n,int k)
{
if(n==k) return k;
int t=f(n-n/k,k);
return t+(t-)/(k-);
}
int main()
{
int t,n,k;
scanf("%d",&t);
while(t--) scanf("%d%d",&n,&k),printf("%d\n",f(n,k));
return ;
}

这个比较难理解,需要推导数学公式。

方法三:打表法

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std; typedef __int64 LL;
LL INF=; LL save[][]; int main()
{
LL ans,b,d,mid;
for(LL i=;i<=;i++)
{
ans=i;
for(LL j=;j<;j++)
{
b=ans,d=INF;
while(b<=d)
{
mid=(b+d)/;
if(mid-mid/i == ans)
break;
if(mid-mid/i>ans)
d=mid-;
else b=mid+;
}
if( (mid-)-(mid-)/i==ans ) ans=mid-;
else ans=mid;
save[i][j]=ans;
}
}
int T;
scanf("%d",&T);
int flag,n,k,cnt;
while(T--)
{
scanf("%d%d",&n,&k);
for(int i=;;i++)
{
if(save[k][i]>n&&save[k][i-]<=n)
{
printf("%I64d\n",save[k][i-]);
break;
}
}
}
return ;
}

很暴力,性能远不如前面,但是容易想。

 
05-08 08:04