约瑟夫环小结
题目大意:
输入初始人数编号为1~n,以及初始密码m,输出出局序列.
解法一:用线段树可解.
Segtree节点存储左右区间和该区间下包含的人数.
void Build(int p,int left,int right)表示从编号为p,区间为[l,r]的节点开始向下建树.
int Update(int p,int id)表示从编号为p的节点开始查询在新队伍中编号为id的人出局,返回该人在最初队伍中的编号.
而temp=(temp+m-1)%seg[1].manum;语句用于求出下一个出局的人在新队伍中的编号id,即传入Update()函数参数中的id.
Update()函数在递归过程中实现了给新队伍人员从1~n重新编号,如果id<=seg[p].manum,即p节点左子树最多能从1编号到seg[p].manum.
说明新队伍中编号为id的人在p节点的左子树中,则继续递归;反之,则在p节点的右子树中.
#include<cstdio> #define maxn 200010 struct Segtree { int l,r; int manum; }seg[4*maxn]; void Build(int p,int left,int right) { seg[p].l=left; seg[p].r=right; if(left==right){ seg[p].manum=1; return; } int mid=(left+right)/2; Build(2*p,left,mid); Build(2*p+1,mid+1,right); seg[p].manum=seg[2*p].manum+seg[2*p+1].manum; } int Update(int p,int id) { seg[p].manum--; if(seg[p].l==seg[p].r) return seg[p].l; if(id<=seg[2*p].manum) Update(2*p,id); else Update(2*p+1,id-seg[2*p].manum); } int main() { int n,m; while(scanf("%d%d",&n,&m)!=EOF){ printf("%d",m); Build(1,1,n); Update(1,m); int temp=m; while(n>=2){ temp=(temp+m-1)%seg[1].manum; if(temp==0) temp=seg[1].manum; int id=Update(1,temp); printf(" %d",id),n--; } printf("\n"); } }
解法二:链表实现
#include<cstdio> #include<cstdlib> using namespace std; typedef struct Lnode { int id; int code; struct Lnode *next; }Lnode,*Linklist; Linklist Createlist(int n)//创建链表 { Lnode *head=NULL,*tail=NULL; for(int i=1;i<=n;i++){ //Lnode *p=(Lnode *)malloc(sizeof(struct Lnode)); Linklist p=(Linklist)malloc(sizeof(Linklist)); p->id=i; printf("输入第%d个人的密码:",i); scanf("%d",&p->code); if(tail) tail->next=p; else head=p; tail=p; } if(tail)//成环 tail->next=head; return tail; } void print(Linklist tail)//输出 { Linklist head=tail; Linklist p=tail->next; printf("每个人的初始序号和初始密码:\n"); while(p!=head){//head标志起点 printf("(%d %d) ",p->id,p->code); p=p->next; } printf("(%d %d)",p->id,p->code); } Linklist play(Linklist& tail,int n,int m)//进行游戏 { Linklist pre,p; pre=tail; printf("出列顺序为:"); while(n>1){ p=pre->next; m==m%n?m%n:n;//= for(int i=1;i<=m-1;i++){ pre=p; p=p->next; } printf("%d ",p->id); //p=p->next pre->next=p->next; m=p->code; n--; } printf("%d\n\n",pre->id);//pre始终指向为待出列人前驱 } int main() { int n,m; printf("输入总人数和初始密码:"); while(scanf("%d",&n),n){ scanf("%d",&m); printf("*人数为0则游戏结束*\n"); Linklist tail=Createlist(n); if(tail){ print(tail); play(tail,n,m); } printf("输入总人数和初始密码:"); } return 0; }
解法三:数组模拟可解
#include<bits/stdc++.h> #define maxn 50 using namespace std; bool in[maxn]; int a[maxn]; int c[maxn],t[maxn]; int main() { memset(in,0,sizeof(in)); int i,j,k,m,n,code,*p; printf("请输入总人数和初始密码:"); scanf("%d%d",&n,&code); p=c; for(i=0;i<n;i++){ *(p+i)=i+1; printf("第%d个人密码:",i+1); scanf("%d",&t[i]);//t[i]中存密码 } i=0,j=0; k=0;//k代表报出号码数 m=0;//m代表退出的人数 while(m<n){ if(!in[i]) k++; if(k==code){ a[j]=(*(p+i));//退出则保存 in[i]=1;//设入队标志 k=0; m++,j++; code=t[i]; code=code%n?code%n:n;//减少循环次数 } i++; if(i==n)//每一个in[i]都判断 i=0; } printf("出列顺序为:"); for(i=0;i<j;i++) printf("%d ",a[i]); return 0; }
未完待续,持续更新中......