POJ 1012 Timing Limit Exceed问题

来源:互联网 发布:卖衣服打折怎么计算法 编辑:程序博客网 时间:2024/06/05 15:23

Description

The Joseph's problem is notoriously known. For those who are not familiar with the original problem: from among n people, numbered 1, 2, . . ., n, standing in circle every mth is going to be executed and only the life of the last remaining person will be saved. Joseph was smart enough to choose the position of the last remaining person, thus saving his life to give us the message about the incident. For example when n = 6 and m = 5 then the people will be executed in the order 5, 4, 6, 2, 3 and 1 will be saved. 

Suppose that there are k good guys and k bad guys. In the circle the first k are good guys and the last k bad guys. You have to determine such minimal m that all the bad guys will be executed before the first good guy. 

Input

The input file consists of separate lines containing k. The last line in the input file contains 0. You can suppose that 0 < k < 14.

Output

The output file will consist of separate lines containing m corresponding to k in the input file.

Sample Input

340

Sample Output

530

       

 在看到POJ 1012这个题之后,首先想到这个问题就是容易,直接用循环查询,就可以啦。于是就写出了下面的程序,谁知道提交竟然会去超时。。。


#include<stdio.h>#include<string.h>#define MAX 28int main(void){    int k,kk,i,m,count=0,countBad=0;    int c[MAX];    scanf("%d",&k);    while(k){m=k+1;kk=2*k;countBad=0;count=0;memset(c,0,sizeof(c));for(i=1;i<=kk;i=(++i)%(kk+1)==0?1:i){//这里表达式3,有点乱。其实,若是以数组下标0开始对人编号,在对有取模运算的表达式时会很方便    if(countBad==k)break;    if(c[i]==1)continue;    count++;    if(count==m && i<=k){m++;count=0;countBad=0;memset(c,0,sizeof(c));i=0;continue;}    else if(count==m){c[i]=1;countBad++;count=0;   }}printf("%d\n",m);scanf("%d",&k);    }    return 0;}


真的会超时的,在算k=10,11时候,在自己的电脑上运行真的需要很长一段时间。

分析上面的算法复杂度:

普通的Joseph环问题,有n个人,开报数m的话,只剩下最后一个人的算法复杂度为:(n-1)*m,这个算法若不是找出最后一个人,若出第k个人的算法复杂度应该是k*m.

所以Joseph在找出第k个人的时候,是和这个环的长度是无关的。


而在上面代码算法中,m是不确定的,我们从m=k+1开始,找出k个人,和算法复杂度为

  (k-1)*m+(k-1)*(m+1)+...+(k-1)*(m+i)+...+>=  (k-1)*m+(k-1)*(m+1)+...+(k-1)*(m+i)+...+k*(m+w)  ,前面用k-1标记,说明我们在后面找不k个bad guys。

>=(k-1)[ m+(m+1)+....+(m+w)]=(k-1)[(w+1)m+w(w+1)/2)]=O(kwm+kw^2/2) , 因为w是不确定的,所以这个算法复杂度是Ω(n)=kw^2. 记号(Ω是>=的意思

在上面程序运行进,当k=1,..13时,w对应的值为27530169441187276321740933134599011358657,  2504881 }-k;

对于2504881^2这样的时间复杂度,确实是很长很长啦。

所以利用枚举算法来解决该问题显然是不行的。所以我们应该用数学的方法来解决该问题。

其实Joseph Problem是有一个递推公式的:‘

环中有n个人,从0开始编号(0,1, ......,n-1)   依次报数m

            第i轮出局的人为f(i)=(f(i-1)+m-1)%(n-i+1),f(0)=0; (参考wikipedia)

注意:第i轮出局的人f(i)返回的不是他在第一轮所在编号,而在第i轮,删除i-1人之后的下标。

如(0,1,2,3,4,5)这6个人报5的话,其下标依次为第一轮:              人        1,2,3,4,5,6

 下标编号:(0,1,2,3,4,5)从第0号开始报数,出局的为5,其编号为f(1)=4 。

       第二轮                人  :    1,2,3,4,6

 下标编号:(0,1,2,3,4)从f(1)=4编号开始报数,从出局的为4,其编号为f(2)=3。

        第三轮                人  :    1,2,3,6

 下标编号:(0,1,2,3)从f(2)=3编号开始报数,从出局的为6,其编号为f(3)=3。

这样可以看出,利用上公式的话,只要找出的前k轮找出的下标大于k就OK了。

利用数学公式,而不是利用枚举的方法,这时的时间复杂度为:我们不能找出其上限,但是能找出其下线,是Ω(n)=kw^2. 记号(Ω是>=的意思)


代码如下:

#include<stdio.h>int query(int k,int m);int main(void){    int k,m;    scanf("%d",&k);    while(k){m=k+1;while(1){    if(query(k,m))break;    if(query(k,m+1)){m++;break;     }    m+=(k+1);}printf("%d\n",m);scanf("%d",&k);    }    return 0;}int query(int k,int m){    int i,f=0,n=k<<1;    for(i=0;i<k;i++){f=(m+f-1)%(n-i);if(f<k)    return 0;    }    return 1;}

提交后,还会超时。


可能是因为第次输入k时,都会重新计算。所以解决方法,将1--13的先计算好,结果保存在数组中。然后有重复的k的时候,直接查找数组就可以了,而不需要重新计算了。

这下就会超时了,代码如下:

#include<stdio.h>//这样都会超时,难道要一次运行完成,把全部运算出来,存在数组中。这样//要是者不行的话,那只能用直接打表的方法了。`int query(int k,int m);int main(void){    int i,k,m;    int a[13];    for(k=1;k<=13;k++){m=k+1;while(1){    if(query(k,m)){a[k-1]=m;break;    }    if(query(k,m+1)){a[k-1]=m+1;break;     }    m+=(k+1);}    }    while(scanf("%d",&k),k){    printf("%d\n",a[k-1]);    }    return 0;}int query(int k,int m){    int i,f=0,n=k<<1;    for(i=0;i<k;i++){f=(m+f-1)%(n-i);if(f<k)    return 0;    }    return 1;}








0 0