1282 约瑟夫问题
时间限制: 1 s
空间限制: 128000 KB
题目等级 : 大师 Master
题目描述 Description
有编号从1到N的N个小朋友在玩一种出圈的游戏。开始时N个小朋友围成一圈,编号为I+1的小朋友站在编号为I小朋友左边。编号为1的小朋友站在编号为N的小朋友左边。首先编号为1的小朋友开始报数,接着站在左边的小朋友顺序报数,直到数到某个数字M时就出圈。直到只剩下1个小朋友,则游戏完毕。
现在给定N,M,求N个小朋友的出圈顺序。
输入描述 Input Description
唯一的一行包含两个整数N,M。(1<=N,M<=30000)
输出描述 Output Description
唯一的一行包含N个整数,每两个整数中间用空格隔开,第I个整数表示第I个出圈的小朋友的编号。
样例输入 Sample Input
5 3
样例输出 Sample Output
3 1 5 2 4
分析 Analysis
现在有一个标准的1-n的递增排列
击鼓传花,每次数到 m 时就要去掉当前这个元素,然后继续从 0 计数
那么定义 sum( i ) 为元素 i 之前的当前实际存在的元素数,在计算过程中,sum( i ) 才是真正的位置
那么给每一个元素一个初始权值 1 ,维护每个元素以自己为端点的前缀和,就能愉快的计算 sum( i ) 啦
那么设 pos 为当前要操作的元素位置,根据 AET(Apparently Easy Theory) 原理,我们知道下一步的 pos = (pos-1)%len
但是我们计算的时候要把pos+1
那么线段树的内容就是维护前缀和且单点置零啦
注意查找
这次的线段树被阉割的非常阉割
代码 Code
1 #include<cstdio> 2 #include<iostream> 3 #define mid (L+R)/2 4 #define lc (rt<<1) 5 #define rc (rt<<1|1) 6 #define maxn 1000000 7 using namespace std; 8 9 int Tree[maxn],n,m; 10 void maintain(int rt){Tree[rt] = Tree[lc]+Tree[rc];} 11 void build(int rt,int L,int R){ 12 if(L == R) Tree[rt] = 1; 13 else{ 14 build(lc,L,mid); 15 build(rc,mid+1,R); 16 maintain(rt); 17 } 18 } 19 void modify(int rt,int L,int R,int pos){ 20 if(L == R) Tree[rt] = 0; 21 else{ 22 if(pos <= mid) modify(lc,L,mid,pos); 23 else modify(rc,mid+1,R,pos); 24 maintain(rt); 25 } 26 } 27 int query(int rt,int L,int R,int val,int remain){ 28 if(L == R) return L; 29 else{ 30 if(val <= Tree[lc]+remain) return query(lc,L,mid,val,remain); 31 else return query(rc,mid+1,R,val,remain+Tree[lc]); 32 } 33 } 34 35 int main(){ 36 scanf("%d%d",&n,&m); 37 38 build(1,1,n); 39 40 int pos = m,ans; 41 for(int i = n;i >= 1;i--){ 42 pos = (pos-1)%(Tree[1]); 43 // printf("###$$%d ",pos+1); 44 ans = query(1,1,n,pos+1,0); 45 printf("%d ",ans); 46 modify(1,1,n,ans); 47 pos += m; 48 // getchar(); 49 } 50 51 return 0; 52 }