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

 

你可能感兴趣的:(【CODEVS1282】约瑟夫问题)