Codevs 1282 约瑟夫问题 线段树

Codevs 1282 约瑟夫问题


首先,建树,根节点 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值