题目描述 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
模拟的话复杂度O(MN) 会超时
求最后出圈人 有O(N)的算法
对于求顺序的 有O(NlogN)的算法
用线段树实现
因为线段树是维护区间信息的数据结构
主要思想就是 根节点0 1 表示是否在圈内 维护一个sumv的线段树
当前出圈人是第pos人 下一次是第pos+n-1人 因为刚才pos出去了 所以要-1 为了防止它大于sumv[1] 所以要%sumv[1] 又为了防止它是第0个人 所以特判一下
注意是人不是编号 而线段树维护的就是在圈内的人的数量
所以可以递归找到是哪个根节点 然后输出它对应的编号
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int lim=30033;
int sumv[lim<<2];
int m,n,want,pos;
int num[lim<<2],rank;
void pushup(int o){sumv[o]=sumv[o<<1]+sumv[o<<1|1];}
void build(int o,int l,int r)
{
if(l==r)
{
sumv[o]=1;
rank++;
num[o]=rank;
return;
}
int m=(l+r)>>1;
build(o<<1,l,m);
build(o<<1|1,m+1,r);
pushup(o);
}
void del(int o,int l,int r)
{
if(l==r)
{
sumv[o]=0;
cout<<num[o]<<' ';
return;
}
int m=(l+r)>>1;
if(want<=sumv[o<<1])del(o<<1,l,m);
else
{
want-=sumv[o<<1];
del(o<<1|1,m+1,r);
}
pushup(o);
}
int main()
{
scanf("%d%d",&m,&n);
n%=m;
if(n==0)n=m;
build(1,1,m);
pos=1;
while(sumv[1])
{
pos=(pos+n-1)%sumv[1];
if(pos==0)pos=sumv[1];
want=pos;
del(1,1,m);
}
return 0;
}