Description
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
Output
Sample Input
3 4 0
Sample Output
5 30
在看到POJ 1012这个题之后,首先想到这个问题就是容易,直接用循环查询,就可以啦。于是就写出了下面的程序,谁知道提交竟然会去超时。。。
#include<stdio.h>
#include<string.h>
#define MAX 28
int 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;
}
分析上面的算法复杂度:
普通的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对应的值为{ 2, 7, 5, 30, 169, 441, 1872, 7632, 1740, 93313, 459901, 1358657, 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;
}