约瑟夫环问题:
任给正整数n、k,按下述方法可得排列1,2,……,n的一个置换:将数字1,2,.. .,n环形排列,按顺时针方向从1开始计数;计满K时输出该为之上的数字(并从环中删去该数字),然后从下一个数字开始继续计数,直到环中所有数字均被输出为止。试编写一算法,对输人的任意正整数n、k,输出相应的置换。
经典的问题,在数据较小的时候,直接模拟即可,O(nk)
#include <stdio.h> int main(int argc,char** argv) { int n,k; scanf("%d %d",&n,&k); int i; int over[1001] = {0}; int now = 0; for (i=1;i<=n;i++){ int work = 0; while ( work < k ) { now ++; if (now > n) now = 1; if (over[now]) continue; work ++; } over[now] = 1; if (i == n){ printf("%d",now); break; } printf("%d ",now); } return 0; }
当数据变大时,就需要使用更省时的算法,有O(nlogn)的,有O(nlogm)的,基本都是建树解决
#include <stdio.h> #include <stdlib.h> #define MAXSIZE 200001 typedef struct TreeNode* Tree; struct TreeNode { Tree Left,Right; int SIZE; int VALUE; }; Tree Forest[MAXSIZE],ROOT[MAXSIZE]; int PREV[MAXSIZE],NEXT[MAXSIZE]; int RESULT[MAXSIZE]; int DEAD[MAXSIZE] = {0}; int n,m; int Height; // helpers int MIN(int x,int y){ if (x < y) return x; return y; } int CalHeight(){ int i = 0,base = 1; while (base < m) { i ++; base *= 2; } return i+1; } int powOfTwo(int i){ int j = 0,base = 1; while (j < i){ j ++; base = 2 * base; } return base; } void PrintTree(int i,Tree T,int NodeNum){ if (!T) return; if (T->VALUE == 0){ //printf("%d %d SIZE=%d ",i,NodeNum,T->SIZE); PrintTree(i,T->Left,2*NodeNum); PrintTree(i,T->Right,2*NodeNum+1); } else printf("%d %d SIZE = %d %d ",i,NodeNum,T->SIZE,T->VALUE); } // int nowNum = 0; Tree makeTree(Tree T,int nowHeight,int i,int NodeNum){ if (!T) { T = (Tree)malloc(sizeof(struct TreeNode)); T->Left = NULL; T->Right = NULL; T->VALUE = 0; T->SIZE = 0; if (nowHeight == Height) { nowNum ++; if (nowNum > m*i|| nowNum > n) { nowNum --; return T; } T->VALUE = nowNum; return T; } } T->Left = makeTree(T->Left,nowHeight+1,i,2*NodeNum); T->Right = makeTree(T->Right,nowHeight+1,i,2*NodeNum+1); return T; } int AdjustTreeSize(Tree T){ if (!T) return 0; if (T->VALUE != 0) { T->SIZE = 1; return T->SIZE; } int size = AdjustTreeSize(T->Left) + AdjustTreeSize(T->Right); T->SIZE = size; return size; } Tree createTree(int i){ Tree root = NULL; root = makeTree(root,1,i,1); AdjustTreeSize(root); return root; } int LEAF1[MAXSIZE],LEAF2[MAXSIZE],total1,total2; void GETLEAF1(Tree T,int nowHeight){ if (!T) return; if (nowHeight == Height){ if (T->VALUE != 0) { total1 ++; LEAF1[total1] = T->VALUE; } return; } GETLEAF1(T->Left,nowHeight+1); GETLEAF1(T->Right, nowHeight+1); return; } void GETLEAF2(Tree T,int nowHeight){ if (!T) return; if (nowHeight == Height){ if (T->VALUE != 0) { total2 ++; LEAF2[total2] = T->VALUE; } return; } GETLEAF2(T->Left,nowHeight+1); GETLEAF2(T->Right,nowHeight+1); return; } int LEAFRES[MAXSIZE],RESNOW = 0,total = 0; Tree InsertElementIntoT(Tree T,int nowHeight){ if (!T) return NULL; if (nowHeight == Height){ RESNOW ++; if (RESNOW > total) { RESNOW --; return T; } T->VALUE = LEAFRES[RESNOW]; return T; } T->Left = InsertElementIntoT(T->Left, nowHeight+1); T->Right = InsertElementIntoT(T->Right, nowHeight+1); return T; } Tree Combine(int t){ Tree T = ROOT[t],T1 = ROOT[NEXT[t]]; // make t and t1 a new t total1 = 0; total2 = 0; RESNOW = 0; GETLEAF1(T,1); GETLEAF2(T1,1); int i; total = 0; for (i=1;i<=total1;i++){ total++; LEAFRES[total] = LEAF1[i]; } for (i=1;i<=total2;i++){ total++; LEAFRES[total] = LEAF2[i]; } T = InsertElementIntoT(T,1); AdjustTreeSize(T); NEXT[t] = NEXT[NEXT[t]]; PREV[NEXT[t]] = t; return T; } int main(int argc,char** argv) { scanf("%d %d",&n,&m); int i; Height = CalHeight(); // buildTree int num = 0; if ( n > n/m*m){ num = n/m + 1; } if ( n ==n/m* m){ num = n/m; } for (i=1;i<=num;i++) { ROOT[i] = createTree(i); PREV[i] = i-1; NEXT[i] = i+1; if (i == num) NEXT[i] = 1; if (i == 1) PREV[i] = num; // printf("%d %d %d ",i,NEXT[i],PREV[i]); // PrintTree(i,ROOT[i],1); } int t = num,COUNT = 0,NXTCOUNT,temp = 0; for (i=1;i<=n;i++){ if (t == NEXT[t]){ while (COUNT < m){ COUNT = COUNT + ROOT[t]->SIZE; } } else while (COUNT < m){ t = NEXT[t]; COUNT = COUNT + ROOT[t]->SIZE; } NXTCOUNT = COUNT - m; temp = 0; Tree NODE = ROOT[t]; while (NODE->VALUE == 0){ NODE->SIZE --; if ((COUNT - NODE->Right->SIZE) < m){ NODE = NODE->Right; } else { // printf("%d %d ",COUNT-NODE->Right->SIZE,m); COUNT = COUNT - NODE->Right->SIZE; NODE = NODE->Left; } } NODE->SIZE = 0; RESULT[i] = NODE->VALUE; NODE->VALUE = 0; // combine section COUNT = NXTCOUNT; if (t != NEXT[t]){ if (ROOT[t]->SIZE + ROOT[NEXT[t]]->SIZE == m){ COUNT = COUNT + ROOT[NEXT[t]]->SIZE; ROOT[t] = Combine(t); // PrintTree(t, ROOT[t],1); } // else if (ROOT[t]->SIZE + ROOT[PREV[t]]->SIZE == m){ // t = PREV[t]; // ROOT[t] = Combine(t); // // PrintTree(t, ROOT[t],1); // } } } for (i=1;i<=n;i++){ if (i == n){ printf("%d",RESULT[i]); break; } printf("%d ",RESULT[i]); } return 0; }
1018只要求输出前几个数,所以可以使用一个很巧妙的函数来做到
long kth(long n, long m, long k) { for (k *= m; k > n; k = k-n+(k-n-1)/(m-1)); return k; }
因为第k个出列的人,肯定是在第mk次报数出列,通过倒推来算出最初的位置。
#include <stdio.h> long kth(long n, long m, long k) { for (k *= m; k > n; k = k-n+(k-n-1)/(m-1)); return k; } int main(int argc,char** argv) { long n,k,t,i; while (scanf("%ld %ld %ld",&n,&k,&t) == 3){ long q[100] = {0}; for (i=0;i<t;i++) { scanf("%ld",&q[i]); } for (i=0;i<t;i++){ if (i == t-1){ printf("%ld ",kth(n,k,q[i])); break; } printf("%ld ",kth(n,k,q[i])); } } return 0; }
参考资料
http://maskray.me/blog/2013-08-27-josephus-problem-two-log-n-solutions
An O(n log m) Algorithm for the Josephus Problem