约瑟夫问题汇总

来源:互联网 发布:阿里云系统看电视 编辑:程序博客网 时间:2024/06/06 14:14

背景

有编号从1NN个小朋友在玩一种出圈的游戏。开始时N个小朋友围成一圈,编号为I+1的小朋友站在编号为I小朋友左边。编号为1的小朋友站在编号为N的小朋友左边。首先编号为1的小朋友开始报数,接着站在左边的小朋友顺序报数,直到数到某个数字K时就出圈。直到只剩下1个小朋友,则游戏完毕。

1、求最后出圈的人

为了方便进行取模,假设还剩n个人的时候编号为0~n-1
令一个人出圈以后从他的下一个开始编号为0,一直到他的前一个编号为n-2,那么不难发现,每个人的旧编号都是新编号加上k mod n的结果,这是因为被删去的数在新的编号法则中编号为n-1。这相当于将k~(n-1)~0~k-1重新编号成0~n-1,再删去n-1,对所有剩余的编号都没有影响
这样,最后出圈的人在仅剩一人时编号为0,每次+k mod 人数 可以递推得剩更多人时胜利者的编号,直至n人
时间效率:O(n)
题目链接:La3882
#include<cstdio>using namespace std;int n,k;int f;int main(){ scanf("%d%d",&n,&k); while(n&&k) { f=0; for(int i=2;i<=n;i++) f=(f+k)%i;printf("%d\n",f+1);scanf("%d%d",&n,&k); }}

2、求倒数第M个出圈的人

与上面的思路类似。
还剩M个人的时候,从0开始数,下一个出圈的编号为(k-1)%M,然后向上递推即可
时间效率 O(n)
题目链接:LA4727
#include<cstdio>#include<algorithm>#include<cstring>using namespace std;int T,n,k,f;int main(){scanf("%d",&T);while(T--){scanf("%d%d",&n,&k);f=(k-1)%3;for(int i=4;i<=n;i++)f=(f+k)%i;printf("%d ",f+1);f=(k-1)%2;for(int i=3;i<=n;i++)f=(f+k)%i;printf("%d ",f+1);f=0;for(int i=2;i<=n;i++)f=(f+k)%i;printf("%d\n",f+1);}}

3、输出完整的出圈顺序

1、模拟法

使用一个环状链表,真正进行报数操作,由于并不困难,在这里不给出代码
时间效率:O(n^2)

2、线段树

建一棵权值线段树,每个节点存储大小在【l,r】之间的数的个数,则可以在线段树上二分,找出剩余n个数字中的第k%n个然后删去
时间效率:O(nlogn)
#include<cstdio>#include<algorithm>#include<cstring>using namespace std;#define lch t*2#define rch t*2+1 #define mid (l+r)/2#define maxn 30005int n,m;int size[maxn*4]; void del(int x,int t=1,int l=1,int r=n){size[t]--;if(l==r) return;x<=mid?del(x,lch,l,mid):del(x,rch,mid+1,r);}int kth(int k,int t=1,int l=1,int r=n){if(l==r) return l;if(k<=size[lch]) return kth(k,lch,l,mid);else return kth(k-size[lch],rch,mid+1,r);}void build(int t=1,int l=1,int r=n){size[t]=r-l+1;if(l==r)return;build(lch,l,mid);build(rch,mid+1,r);}int main(){ scanf("%d%d",&n,&m); build(); int lastdel=0; while(size[1]) { lastdel=(lastdel+m)%size[1]; if(lastdel==0) lastdel=size[1];int ans;del(ans=kth(lastdel)); lastdel--;printf("%d ",ans);}}




1 0
原创粉丝点击