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;
}