【CODEVS1282】约瑟夫问题
Description
有编号从1到N的N个小朋友在玩一种出圈的游戏。开始时N个小朋友围成一圈,编号为I+1的小朋友站在编号为I小朋友左边。编号为1的小朋友站在编号为N的小朋友左边。首先编号为1的小朋友开始报数,接着站在左边的小朋友顺序报数,直到数到某个数字M时就出圈。直到只剩下1个小朋友,则游戏完毕。
现在给定N,M,求N个小朋友的出圈顺序。
Input
唯一的一行包含两个整数N,M。(1<=N,M<=30000)
Output
唯一的一行包含N个整数,每两个整数中间用空格隔开,第I个整数表示第I个出圈的小朋友的编号。
Sample Input
5 3
Sample Output
3 1 5 2 4
#include<iostream> #include<cstdio> using namespace std; const int maxn=100000; int cnt=0; struct treetype{ int lptr,rptr,Fat; //左右孩子及父节点指针 int Left,Right; int sum; //[Left,Right)中的人数 }t[2*maxn]; void buildtree(int ll,int rr){ int cur=++cnt; t[cur].Left=ll; t[cur].Right=rr; if (t[cur].Right!=t[cur].Left+1){ t[cur].lptr=cnt+1; t[cnt+1].Fat=cur; buildtree (ll,(ll+rr)/2); t[cur].rptr=cnt+1; t[cnt+1].Fat=cur; buildtree ((ll+rr)/2,rr); t[cur].sum=t[t[cur].lptr].sum+t[t[cur].rptr].sum; }else{ t[cur].lptr=0; t[cur].rptr=0; t[cur].sum=1; } } int calc(int x){ //找圈中的第x个人(出圈),并更新线段树 int k=1; while (t[1].sum<x) x-=t[1].sum; //x(out)<=t[1].sum,使x在圈内 while (t[k].Left!=t[k].Right-1){ //非单位区间 if (t[t[k].lptr].sum<x){ //在右区间 x-=t[t[k].lptr].sum; k=t[k].rptr; }else k=t[k].lptr; //在左区间 }//出圈者是t[k].Left printf("%d ",t[k].Left); for (int kk=k;kk>0;kk=t[kk].Fat) t[kk].sum--; //更新线段树 return t[k].Left; } int query(int k,int ll,int rr){ //查询区间[ll,rr)中的人数 if (ll<=t[k].Left &&rr>=t[k].Right) return t[k].sum; else{ int ans=0; if (ll<(t[k].Left+t[k].Right)/2) ans+=query(t[k].lptr,ll,rr); if (rr>(t[k].Left+t[k].Right)/2) ans+=query(t[k].rptr,ll,rr); return ans; } } int main(){ int n,m,k; scanf("%d%d",&n,&m);//n个人 数到m报数 buildtree(1,n+1); int out=m; for (int i=1;i<=n;i++){ k=calc(out); //k出圈 k=query(1,1,k+1); //[1,k+1)中的人数 out=k+m; //out可能大于t[1].sum } return 0; }