有编号从1到N的N个小朋友在玩一种出圈的游戏。开始时N个小朋友围成一圈,编号为I+1的小朋友站在编号为I小朋友左边。编号为1的小朋友站在编号为N的小朋友左边。首先编号为1的小朋友开始报数,接着站在左边的小朋友顺序报数,直到数到某个数字M时就出圈。直到只剩下1个小朋友,则游戏完毕。
现在给定N,M,求N个小朋友的出圈顺序。
唯一的一行包含两个整数N,M。(1<=N,M<=30000)
唯一的一行包含N个整数,每两个整数中间用空格隔开,第I个整数表示第I个出圈的小朋友的编号。
5 3
类型:线段树 难度:33 1 5 2 4
题意:n个人围成一圈,顺时针编号1-n,给出一个m,从第1个人开始数,数到第m个人就出圈,然后从出圈的下一个人继续数m个。。。求出圈的顺序。
分析:用线段树存储每个区间中剩余的人数,即在圈内则置为1,出圈的置为0,用realm表示当前查找的是还在圈中的第几个人,即线段树(1,x)区间的和为realm,那么x就是这次出圈的人。例:若当前的序列为10110,realm=2,那么这次出圈的人是3。
计算realm:realm初始化为1,每次更新realm = (realm-2+m)%i+1,i=n,...,1,这个式子的解释:首先考虑(realm+m)%i为下一个查找realm,发现每次去掉一个人后,相当于从realm之前的一个人开始数,所以变成(realm-1+m)%i,但是%i的结果可能为0,所以在最后+1,前面再-1。
查找x满足(1,x)的和为realm,且x为1:类似二分查找,先查找线段树左子树的和,若小于等于realm,则x在左子树,递归查找;若大于realm,查找右子树,realm更新为realm-sum[left],直到查找区间的左右边界相等,即返回结果。
代码:
#include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> using namespace std; #define lson l , m , rt << 1 #define rson m + 1 , r , rt << 1 | 1 const int maxn = 100010; int n,m; int sum[maxn<<2]; void PushUP(int rt) { //把当前结点的信息更新到父结点 sum[rt] = sum[rt<<1] + sum[rt<<1|1]; } void build(int l,int r,int rt) { //建立线段树 if (l == r) { sum[rt] = 1; return ; } int m = (l + r) >> 1; build(lson); build(rson); PushUP(rt); } void update(int p,int v,int l,int r,int rt) { //单点增减 if (l == r) { sum[rt] = v; return ; } int m = (l + r) >> 1; if (p <= m) update(p , v , lson); else update(p , v , rson); PushUP(rt); } int findpos(int len,int l,int r,int rt) { //区间求和 if (l==r) { return l; } int left = rt<<1; int m = (l+r)>>1; if(len <= sum[left]) return findpos(len,lson); return findpos(len-sum[left],rson); } int main() { scanf("%d%d",&n,&m); build(1,n,1); int realm = 1,now; for(int i=n; i>0; i--) { realm = (realm-2+m)%i+1; now = findpos(realm,1,n,1); printf("%d ",now); update(now,0,1,n,1); } printf("\n"); }