首先,建树,根节点 l = 1, r = n, sum = n;
sum 指的是在这个区间内 还剩下的人数。
主函数一个 while 循环, 一直循环到根节点 为 0; 也就是所有人都出圈了。
另外:根节点的sum (tree[1].sum)代表还没出圈的还有多少人。
我们可以将 圆圈看为一个队列,最后一个人的后面是 1、
nxt 为 从当前点往后 m 个人之后(可能到了尾部又从1开始) 第 nxt 个人。并不是 编号为 nxt 的人。
比如:
现在队列为 1 3 5 7。
2、4、6出圈了。
如果我nxt = 2, 那么我指的是从队列头 开始往后2个人, 就是编号为3 的人。
搞清楚了这一点之后开始考虑如何找 当前队列中第 nxt 个人。
利用change 操作, 从线段树根节点往下走, 因为我们找的是 第 nxt 个人,所以,如果左边节点里面的人数 小于 nxt 那么我们要找的人肯定在右边节点。 就变成了 找 右边节点中的第 nxt - x 个人。 另外,我们走过的节点的 sum 需要–, 因为我们已经确定我们要找的人 在这个区间里面了, 我们找到之后 另 这个人的 sum = 0; 表示没有这个人了。 也可以 – , 反正这是最底层节点, sum = 1;
再利用change 返回我们找到的这个点的编号,就行了。
代码:
#include <iostream>
#include <queue>
#include <cstring>
#include <cstdio>
#define L(x) (x << 1)
#define R(x) (x << 1 | 1)
using namespace std;
const int MAXN = 30000 + 3;
int n, m;
struct T{
int l, r, sum;
}tree[MAXN << 2];
void update(int p)
{
tree[p].sum = tree[L(p)].sum + tree[R(p)].sum;
}
void build(int l, int r, int p)
{
tree[p].l = l;
tree[p].r = r;
if(l == r)
{
tree[p].sum = 1;
return;
}
int mid = (l+r)/2;
build(l,mid,L(p));
build(mid+1,r,R(p));
update(p);
return;
}
int change(int x, int p = 1)
{
tree[p].sum --;
if(tree[p].l == tree[p].r)
{
tree[p].sum = 0;
return tree[p].l;
}
if(tree[L(p)].sum >= x)
return change(x,L(p));
else return change(x-tree[L(p)].sum, R(p));
}
int main()
{
cin >> n >> m;
build(1,n,1);
int nxt = 1;
while(tree[1].sum)
{
nxt = (nxt+m-1)%tree[1].sum;
if(!nxt) nxt = tree[1].sum;
printf("%d ", change(nxt));
}
return 0;
}