【acm】杀人游戏(hdu2211)

题目链接: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

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

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

我们可以看到,第三行比第二行新插进来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。

 

 符合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-1)/(k-1);  
}  
int main()  
{  
    int t,n,k;  
    scanf("%d",&t);  
    while(t--) scanf("%d%d",&n,&k),printf("%d\n",f(n,k));  
    return 0;  
}  

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

方法三:打表法

 
#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

typedef __int64 LL;
LL INF=2147483648;

LL save[101][2100];

int main()
{
    LL ans,b,d,mid;
    for(LL i=3;i<=100;i++)
    {
        ans=i;
        for(LL j=0;j<2000;j++)
        {
            b=ans,d=INF;
            while(b<=d)
            {
                mid=(b+d)/2;
                if(mid-mid/i == ans)
                    break;
                if(mid-mid/i>ans)
                    d=mid-1;
                else b=mid+1;
            }
            if( (mid-1)-(mid-1)/i==ans ) ans=mid-1;
            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=1;;i++)
        {
            if(save[k][i]>n&&save[k][i-1]<=n)
            {
                printf("%I64d\n",save[k][i-1]);
                break;
            }
        }
    }
    return 0;
}

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

 
posted @ 2016-11-03 16:58  _Boxer  阅读(805)  评论(0编辑  收藏  举报